(function (angular) {
	'use strict';
	var defaultInputClass = 'input-sm',
		defaultGridClass = 'col-sm-12',
		defaultTooltipPlacement = 'top-right',
		defaultTooltipTemplateUrl = 'fs/layout/template/tooltipTpl.html',
		defaultTooltipClass = 'input-tooltip',
		defaultLabel1 = '<label class="control-label"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span></label>',
		defaultLabel2 = '<label class="control-label" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span></label>';
	var dependencies = ['fs.dialog', 'fs.http', 'fs.authorization', 'fs.common', 'fs.validator', 'ui.bootstrap', 'colorpicker.module', 'angular-clipboard', 'ui.bootstrap.contextMenu', 'cfp.hotkeys', 'angularFileUpload', 'chart.js', 'ui.grid', 'ui.grid.autoResize', 'ui.grid.moveColumns', 'ui.grid.pagination', 'ui.grid.resizeColumns', 'ui.grid.selection', 'ui.grid.treeBase', 'ui.grid.treeView', 'ui.grid.cellNav', 'ui.grid.emptyBaseLayer', 'ui.grid.expandable', 'ui.grid.exporter', 'ui.grid.grouping'];
	angular.module("fs.component", dependencies)
		.directive("fsTable", fsTable)
		.directive("fsInnerList", fsInnerList)
		.directive("fsList", fsList)
		//.directive("fsAside", fsAside)
		.directive("fsGridLayout", fsGridLayout)
		.directive('fsGroup', fsGroup)
		.directive('fsGrid', fsGrid)
		.directive('fsColor', fsColor)
		.directive('fsLabel', fsLabel)
		.directive("fsLink", fsLink)
		.directive('fsImage', fsImage)
		.directive('fsText', fsText)
		.directive('fsPassword', fsPassword)
		.directive('fsRadio', fsRadio)
		.directive('fsRadioCard', fsRadioCard)
		.directive('fsCheckbox', fsCheckbox)
		.directive('fsCheckboxGroup', fsCheckboxGroup)
		.directive('fsDate', fsDate)
		.directive('fsDateRange', fsDateRange)
		.directive('fsTime', fsTime)
		.directive('fsCalendarDay', fsCalendarDay)
		.directive('fsTextarea', fsTextarea)
		.directive('fsSelect', fsSelect)
		.directive('fsCustomerSelect', fsCustomerSelect)
		.directive('fsMultipleSelect', fsMultipleSelect)
		.directive('fsTypeahead', fsTypeahead)
		.directive('fsSensitive', fsSensitive)
		.directive('fsMoney', fsMoney)
		.directive('fsMoneyRange', fsMoneyRange)
		.directive('fsNumber', fsNumber)
		.directive('fsNumberRange', fsNumberRange)
		.directive('fsPercentage', fsPercentage)
		.directive('fsPercentageRange', fsPercentageRange)
		.directive('fsLookupText', fsLookupText)
		.directive('fsLookupMultiple', fsLookupMultiple)
		.directive("fsButtons", fsButtons)
		.directive("fsButton", fsButton)
		.directive("fsGridFilter", fsGridFilter)
		.directive("fsGridPrefix", fsGridPrefix)
		.directive("fsFileUpload", fsFileUpload)
		.directive("fsCard", fsCard)
		.directive("fsListGroup", fsListGroup)
		.directive("fsUiGridCompare", fsUiGridCompare)
		.directive("fsTabset", fsTabset)
		.directive("fsTab", fsTab)
		.directive('fsTabHeadingTransclude', fsTabHeadingTransclude)
		.directive('fsTabContentTransclude', fsTabContentTransclude)
		.directive('fsScrollbar', fsScrollbar)
		.directive('fsCustomerSelectItem', fsCustomerSelectItem)
		.directive('fsDashboardPanel', fsDashboardPanel)
		.directive('fsInclude', fsIncludeDirective)
		.run(setupTemplate);


	//<div fs-tabset active="active" type="type" vertical="vertical" justified="justified"/>
	fsTabset.$inject = ['$templateCache'];
	function fsTabset($templateCache) {
		return {
			transclude: true,
			replace: true,
			scope: {},
			bindToController: {
				active: '=?',
				type: '@'
			},
			controller: 'UibTabsetController',
			controllerAs: 'tabset',
			template: function (element, attrs) {
				return $templateCache.get(attrs.templateUrl || 'fs/template/tabs/tabset.html');
			},
			link: function (scope, element, attrs) {
				scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false;
				scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false;
			}
		};
	}

	// <div fs-tab heading="heading" index="index" classes="classes" select="select" deselect="deselect"/>
	fsTab.$inject = ['$parse', '$templateCache', '$timeout'];
	function fsTab($parse, $templateCache, $timeout) {
		return {
			require: ['^fsTabset', '^^?form'],
			replace: true,
			template: function (element, attrs) {
				return $templateCache.get(attrs.templateUrl || 'fs/template/tabs/tab.html');
			},
			transclude: true,
			scope: {
				heading: '@',
				index: '=?',
				classes: '@?',
				onSelect: '&select', // This callback is called in contentHeadingTransclude
				// once it inserts the tab's content into the dom
				onDeselect: '&deselect'
			},
			controller: function () { },
			controllerAs: 'tab',
			link: function (scope, elm, attrs, ctrls, transclude) {
				var tabsetCtrl = ctrls[0], topFormCtrl = getTopFormCtrl(ctrls[1]), timers = [];
				scope.$on("$destroy", function (event) {
					angular.forEach(timers, function (timer, index) {
						$timeout.cancel(timer);
					});
				});
				scope.disabled = false;
				tabsetCtrl.loaded = tabsetCtrl.loaded || [];
				tabsetCtrl.loaded[scope.index] = (tabsetCtrl.active == scope.index || (tabsetCtrl.active === undefined && scope.index == 0) || false);
				if (topFormCtrl) {
					topFormCtrl.tabLoaded = tabsetCtrl.loaded;
				}
				/*if (tabsetCtrl.loaded[scope.index] == false) {
					timers.push($timeout(function() {
						tabsetCtrl.loaded[scope.index] = true;
					}, 4000 + scope.index * 2000));
				}*/
				if (attrs.disable) {
					scope.$parent.$watch($parse(attrs.disable), function (value) {
						scope.disabled = !!value;
					});
				}
				if (angular.isUndefined(attrs.index)) {
					if (tabsetCtrl.tabs && tabsetCtrl.tabs.length) {
						scope.index = Math.max.apply(null, tabsetCtrl.tabs.map(function (t) {
							return t.index;
						})) + 1;
					} else {
						scope.index = 0;
					}
				}
				if (angular.isUndefined(attrs.classes)) {
					scope.classes = '';
				}
				scope.select = function (evt) {
					if (!scope.disabled) {
						var index;
						for (var i = 0; i < tabsetCtrl.tabs.length; i++) {
							if (tabsetCtrl.tabs[i].tab === scope) {
								scope.index = index = i;
								tabsetCtrl.loaded[index] = true;
								break;
							}
						}
						tabsetCtrl.select(index, evt);
					}
				};
				scope.$root.count = scope.$root.count ? scope.$root.count : 0;
				scope.$watch(function () {
					return tabsetCtrl.active;
				}, function (newValue) {
					tabsetCtrl.loaded[newValue] = true;
				});
				tabsetCtrl.addTab(scope);
				scope.$on('$destroy', function () {
					tabsetCtrl.removeTab(scope);
				});
				scope.$transcludeFn = transclude;
			}
		};
	}

	function fsTabHeadingTransclude() {
		return {
			restrict: 'A',
			require: '^fsTab',
			link: function (scope, elm) {
				scope.$watch('headingElement', function updateHeadingElement(heading) {
					if (heading) {
						elm.html('');
						elm.append(heading);
					}
				});
			}
		};
	}

	function fsTabContentTransclude() {
		return {
			restrict: 'A',
			require: '^fsTabset',
			controller: ['$scope', '$attrs', function (scope, attrs) {
				this.getTabIndex = function () {
					var tab = scope.$eval(attrs.fsTabContentTransclude).tab;
					return tab.index;
				};
			}],
			link: function (scope, elm, attrs) {
				var tab = scope.$eval(attrs.fsTabContentTransclude).tab;
				// Now our tab is ready to be transcluded: both the tab heading area
				// and the tab content area are loaded. Transclude 'em both.
				tab.$transcludeFn(tab.$parent, function (contents) {
					angular.forEach(contents, function (node) {
						if (isTabHeading(node)) {
							// Let tabHeadingTransclude know.
							tab.headingElement = node;
						} else {
							elm.append(node);
						}
					});
				});
			}
		};

		function isTabHeading(node) {
			return node.tagName
				&& (node.hasAttribute('uib-tab-heading') || node.hasAttribute('data-uib-tab-heading') || node.hasAttribute('x-uib-tab-heading') || node.tagName.toLowerCase() === 'uib-tab-heading'
					|| node.tagName.toLowerCase() === 'data-uib-tab-heading' || node.tagName.toLowerCase() === 'x-uib-tab-heading' || node.tagName.toLowerCase() === 'uib:tab-heading');
		}
	}

	// <fs-inner-list ng-model="vm.company.table" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsInnerList.$inject = ['$compile', '$templateCache'];
	function fsInnerList($compile, $templateCache) {
		return {
			restrict: 'AE',
			scope: true,
			require: ['^fsGrid', 'fsInnerList'],
			controller: ['$scope', 'gridService', function fsInnerListController($scope, gridService) {
				$scope.fsGridConfig.tableConfig.model = 'ngModel.' + $scope.fsGridConfig.model;
				$scope.fsGridConfig.gridOptions = gridService.createInnerGridOptions($scope.fsGridConfig.appScope,
					$scope.fsGridConfig.tableConfig,
					$scope.fsGridConfig.tableConfig.selection,
					$scope.fsGridConfig.tableConfig.apiBoundName);
				$scope.fsGridConfig.appScope[$scope.fsGridConfig.model + "GridOptions"] = $scope.fsGridConfig.gridOptions;
			}],
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var content, contentEL;
				$element.addClass('fsInnerList');
				initComponent(ctrls[1], ctrls[0]);
				contentEL = $templateCache.get('fs/layout/template/fsInnerList.html');
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	// <fs-list ng-model="vm.company.table" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsList.$inject = ['$compile', '$templateCache'];
	function fsList($compile, $templateCache) {
		return {
			restrict: 'AE',
			scope: true,
			require: ['^fsGrid', 'fsList'],
			controller: ['$scope', 'gridService', function fsListController($scope, gridService) {
				$scope.fsGridConfig.tableConfig.model = 'ngModel.' + $scope.fsGridConfig.model;
				$scope.fsGridConfig.gridOptions = gridService.createGridOptions($scope.fsGridConfig.appScope,
					$scope.fsGridConfig.tableConfig,
					$scope.fsGridConfig.tableConfig.pagination,
					$scope.fsGridConfig.tableConfig.selection,
					$scope.fsGridConfig.tableConfig.sort,
					$scope.fsGridConfig.tableConfig.apiBoundName);
				$scope.fsGridConfig.appScope[$scope.fsGridConfig.model + "GridOptions"] = $scope.fsGridConfig.gridOptions;
			}],
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var content;
				$element.addClass('fsInnerList');
				initComponent(ctrls[1], ctrls[0]);
				contentEL = $templateCache.get('fs/layout/template/fsList.html');
				contentEL = angular.element(content);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	// <div fs-table fs-config="tableOptions" fs-verify-model="verifyModel" fs-validator-config="validatorConfig"/>
	fsTable.$inject = ['$rootScope', '$q', '$parse', '$compile', '$injector'];
	function fsTable($rootScope, $q, $parse, $compile, $injector) {
		return {
			restrict: 'AE',
			scope: true,
			require: ['^^?form', '^^?fsTabContentTransclude', 'fsTable'],
			controller: function () { },
			controllerAs: 'fsTableController',
			compile: function ($element, $attrs) {
				$element.addClass('fsTable');
				return {
					pre: function ($scope, $element, $attrs, ctrls, $transclude) {
						var formCtrl = ctrls[0], tabController = ctrls[1], fsTableCtrl = ctrls[2], watchers = [], watcher, watched, pageLoaded, contentEL, content;
						var topFormCtrl = getTopFormCtrl(formCtrl);
						registerFormCtrls(topFormCtrl, formCtrl);
						$scope.$on('$destroy', function () {
							while (watchers.length) {
								(watchers.shift() || angular.noop)();
							}
						});
						watcher = $scope.$parent.$watch($attrs.fsConfig, function (gridOptions) {
							var template = '';
							if (gridOptions && (fsTableCtrl.gridOptions != gridOptions || !angular.equals(fsTableCtrl.gridOptions, gridOptions))) {
								fsTableCtrl.gridOptions = gridOptions;
								if (!watched) {
									watched = true;
									contentEL = '<div ng-if="!' + $attrs.fsConfig + '.rebuild" {{fsTableCtrl.gridOptions.rebuild}} ui-grid="' + $attrs.fsConfig + '" ';
									if (gridOptions.enableColumnResizing) {
										contentEL += 'ui-grid-resize-columns ';
									}
									if (gridOptions.enablePagination) {
										contentEL += 'ui-grid-customer-pagination ';
									}
									if (gridOptions.enableOperation) {
										contentEL += 'ui-grid-operation ';
									}
									if (gridOptions.enableEolOperation) {
										contentEL += 'ui-grid-eol-operation ';
									}
									if (gridOptions.enableGrouping) {
										contentEL += 'ui-grid-grouping ui-grid-tree-view ';
									}
									if (gridOptions.enableAutoResize == 1) {
										contentEL += 'ui-grid-customer-auto-resize ';
									} else if (gridOptions.enableAutoResize == 2) {
										contentEL += 'ui-grid-auto-resize ';
									}
									if (gridOptions.enableRowSelection) {
										contentEL += 'ui-grid-selection ';
									}
									if (gridOptions.enableColumnMoving) {
										contentEL += 'ui-grid-move-columns ';
									}
									//if (gridOptions.enablePinning) {
									contentEL += 'ui-grid-pinning ';
									//}
									contentEL += '></div>';

									contentEL = angular.element(contentEL);
									content = $compile(contentEL)($scope);
									// Prevent jQuery cache memory leak (template is now redundant after linking)
									contentEL.remove();
									$element.append(content);
									watchers.push($scope.$watch(gridOptions.data, function (data) {
										if (topFormCtrl) {
											validateForm($scope, data, gridOptions, topFormCtrl, tabController, formCtrl);
											$rootScope.initFormTableState($scope, gridOptions, topFormCtrl.$name);
										}
										/*if (!pageLoaded && (!topFormCtrl || !topFormCtrl.formValidation)) {
											//content = $templateCache.get('fs/layout/template/fsTable.html');
											content = content.replace(/TABLE_CONFIG/g, $attrs.fsConfig);
											content = $compile(angular.element(content))($scope);
											$element.append(content);
											pageLoaded = true;
										}*/
									}));
								}
								if (!gridOptions.prepared) {
									prepareFsConfig(gridOptions.appScope || $scope.$parent, gridOptions);
								}
							}
						});
						watchers.push(watcher);
						if (topFormCtrl) {
							topFormCtrl.saveValidateMessage = topFormCtrl.saveValidateMessage || function (formCtrl, validateResult) {
								if (formCtrl) {
									formCtrl.validateErrors = formCtrl.validateErrors || {};
									formCtrl.validateErrors[validateResult.field + '.' + validateResult.rule] = validateResult.messages;
								}
							};
							topFormCtrl.removeValidateMessage = topFormCtrl.removeValidateMessage || function (formCtrl, validateResult) {
								if (formCtrl && formCtrl.validateErrors && formCtrl.validateErrors[validateResult.field + '.' + validateResult.rule]) {
									delete formCtrl.validateErrors[validateResult.field + '.' + validateResult.rule];
								}
							};
						}
					}
				};
			}
		};
		function prepareFsConfig(appScope, gridOptions) {
			if (gridOptions) {
				angular.forEach(gridOptions.columnDefs, function (columnDef) {
					columnDef.appScope = appScope;
					columnDef.gridClass = columnDef.gridClass || defaultGridClass;
					columnDef.inputClass = columnDef.inputClass || defaultInputClass;
					if (columnDef.tooltip) {
						columnDef.tooltip = angular.isArray(columnDef.tooltip) ? columnDef.tooltip : [columnDef.tooltip];
					}
					columnDef.textAlign = columnDef.textAlign || (/.*(amount|amt)/i.test(columnDef.model) ? 'right' : 'left');
					columnDef.tooltipClass = columnDef.tooltipClass || defaultTooltipClass;
					columnDef.tooltipClass = columnDef.tooltipClass || defaultTooltipClass;
					columnDef.tooltipPlacement = columnDef.tooltipPlacement || defaultTooltipPlacement;
					columnDef.tooltipTemplateUrl = columnDef.tooltipTemplateUrl || defaultTooltipTemplateUrl;
					if (columnDef.uppercase == undefined) {
						columnDef.uppercase = true;
					}
					if (columnDef.validatorConfig) {
						gridOptions.validatorConfig = columnDef.validatorConfig;
					}
					if (gridOptions.validatorConfig) {
						columnDef.validatorConfig = gridOptions.validatorConfig;
						prepareGridRules($parse, columnDef, columnDef.validatorConfig);
					}
				});
				gridOptions.prepared = true;
			}
		}
		function validateForm($scope, gridDatas, gridOptions, topFormCtrl, tabController, formCtrl) {
			var promise, promises = [], defered;
			if (!tabController || !gridOptions || !gridOptions.prepared || !topFormCtrl.formValidation) {
				return;
			}
			defered = topFormCtrl.formValidation[tabController.getTabIndex()] ? topFormCtrl.formValidation[tabController.getTabIndex()].defered : $q.defer();

			if (!gridDatas || gridDatas.length == 0 || !topFormCtrl.formValidation[tabController.getTabIndex()]
				|| topFormCtrl.formValidation[tabController.getTabIndex()].validated !== false
				|| !gridOptions.validatorConfig) {
				topFormCtrl.formValidation[tabController.getTabIndex()] = null;
				topFormCtrl.tabLoaded[tabController.getTabIndex()] = false;
				defered.resolve();
				return;
			}
			topFormCtrl.formValidation[tabController.getTabIndex()] = null;
			angular.forEach(gridOptions.columnDefs, function (gridConfig) {
				if (gridConfig.rules) {
					angular.forEach(gridConfig.rules.ruleInfos, function (rule) {
						var ruleName = angular.lowercase(rule.ruleName);
						if ("conditiondependent" != ruleName) {
							angular.forEach(gridDatas, function (formEntity) {
								var d = $q.defer(), validateResult = {
									'field': gridConfig.field || gridConfig.model,
									'rule': rule.ruleName,
									'messages': null
								};
								rule.fieldLabel = gridConfig.label;
								rule.ngModel = formEntity;
								rule.operatedField = gridConfig.field || gridConfig.model;
								rule.modelType = gridConfig.displayName;
								promises.push(d.promise);
								$injector.get(ruleName).validate($parse(gridConfig.model)(formEntity), undefined, rule).then(function (e) {
									validateResult.invalid = false;
									topFormCtrl.removeValidateMessage(formCtrl, validateResult);
									d.resolve();
								}, function (e) {
									validateResult.invalid = true;
									validateResult.messages = e.error;
									topFormCtrl.saveValidateMessage(formCtrl, validateResult);
									d.resolve();
								});
							});
						}
					});
				}
			});
			$q.all(promises).then(function (e) {
				topFormCtrl.formValidation[tabController.getTabIndex()] = null;
				topFormCtrl.tabLoaded[tabController.getTabIndex()] = false;
				defered.resolve();
			});
		}
	}

	// <div fs-dashboard-panel fs-config="panelConfig"/>
	fsDashboardPanel.$inject = ['$log', '$injector', '$document'];
	function fsDashboardPanel($log, $injector, $document) {
		function isJsonLike(str) {
			var jsonStart = str.match(/^\[|^\{(?!\{)/);
			return jsonStart && { '[': /]$/, '{': /}$/ }[jsonStart[0]].test(str);
		}
		return {
			restrict: 'A',
			scope: false,
			require: 'fsDashboardPanel',
			controller: function () { },
			controllerAs: 'fsDashboardPanelController',
			compile: function ($element, $attrs) {
				$element.addClass('fsDashboardPanel');
				return {
					pre: function ($scope, $element, $attrs, ctrl, $transclude) {
						var contentEL, content, watcher, watchers = [], log = $log.getInstance('fsDashboardPanel');
						var panelConfig = $scope.$eval($attrs.fsConfig);
						$scope.$on('$destroy', function () {
							while (watchers.length) {
								(watchers.shift() || angular.noop)();
							}
						});
						//						contentEL = $templateCache.get('fs/layout/template/fsDashboardPanel.html');
						//						contentEL = angular.element(contentEL);
						//						content = $compile(contentEL)($scope);
						//						// Prevent jQuery cache memory leak (template is now redundant after linking)
						//						contentEL.remove();
						//						$element.append(content);
						watcher = $scope.$watch(function () {
							return $element.hasClass('col-sm');
						}, function (inited, old) {
							if (inited && !old) {
								panelConfig.height = panelConfig.minHeight - 98;
								if (panelConfig.panelParams && isJsonLike(panelConfig.panelParams)) {
									panelConfig.panelParams = angular.fromJson(panelConfig.panelParams);
									panelConfig.width = Math.round(($element[0].offsetWidth - 73) * (panelConfig.panelParams.chartWidth || 1));
								} else {
									panelConfig.width = $element[0].offsetWidth - 73;
								}
								panelConfig.appendTo = angular.element($document[0].querySelector('#' + panelConfig.panelCode + panelConfig.pagePanelId)) || $element,
								$injector.get('dashboardPanelService').addPanel(panelConfig);
							}
						});
						watchers.push(watcher);
					}
				};
			}
		};
	}

	//<div fs-aside fs-config="asideConfig"/>
	fsAside.$inject = ['$q', '$compile'];
	function fsAside($q, $compile) {
		return {
			restrict: 'A',
			scope: false,
			require: '^^?form',
			controller: ['$scope', function ($scope) {
				this.showHelp = function (help) {
					$scope.help = help;
				};
				this.removeHelp = function () {
					delete $scope.help;
				};
			}],
			controllerAs: 'fsAsideController',
			compile: function ($element, $attrs) {
				$element.addClass('fsAside');
				return {
					pre: function ($scope, $element, $attrs, formCtrl, $transclude) {
						var watchers = [], watcher, asideLeftWidth, asideRightWidth = 212;
						$scope.$on('$destroy', function () {
							while (watchers.length) {
								(watchers.shift() || angular.noop)();
							}
						});
						watcher = $scope.$watch('app.settings.collapseRightAside', function (collapseRightAside) {
							var height, uiGrid = $element[0].querySelector('.ui-grid');
							if ($scope.asideConfig && collapseRightAside === false && uiGrid && uiGrid.offsetWidth > asideRightWidth) {
								asideLeftWidth = uiGrid.offsetWidth;
								height = uiGrid.style.height;
								uiGrid.removeAttribute("style");
								uiGrid = angular.element(uiGrid);
								uiGrid.css({ height: height, width: (asideLeftWidth - asideRightWidth) + 'px' });
								$timeout(function () {
									uiGrid[0].removeAttribute("style");
									uiGrid.css({ height: height });
								}, 200);
							}
						});
						watchers.push(watcher);
						if ($attrs.fsAside) {
							watcher = $scope.$watch($attrs.fsAside, function (asideConfig) {
								if (asideConfig == $scope.asideConfig) return;
								$scope.asideConfig = asideConfig;
								if ($scope.asideConfig) {
									$element.addClass("app-content-body-left");
									if ($scope.asideConfig.templateUrl) {
										$scope.asideConfig.templateUrl = $scope.asideConfig.templateUrl.replace('${theme}', $scope.app.settings.theme);
									}
								}
							});
							watchers.push(watcher);
						} else {
							$scope.asideConfig = $scope.$eval('vm.layoutConfig.asideConfig');
							if ($scope.asideConfig) {
								$element.addClass("app-content-body-left");
								if ($scope.asideConfig.templateUrl) {
									$scope.asideConfig.templateUrl = $scope.asideConfig.templateUrl.replace('${theme}', $scope.app.settings.theme);
								}
							}
						}
						$q.resolve($templateCache.get('waf/view/' + $scope.app.settings.theme + '/template/aside.html')).then(function (content) {
							content = $compile(angular.element(content))($scope);
							$element.after(content);
						});
					}
				};
			}
		};
	}

	// <fs-grid-layout ng-model="vm.company" fs-verify-model="vm.companyOld" fs-config="vm.layoutConfig" fs-validator-config="vm.validatorConfig"/>
	fsGridLayout.$inject = ['$templateCache', '$rootScope', '$q', '$log', '$parse', '$filter', '$injector', 'formatService'];
	function fsGridLayout($templateCache, $rootScope, $q, $log, $parse, $filter, $injector, formatService) {
		// Initialization
		var log = $log.getInstance('gridLayout');
		function getAppScope(scope) {
			if (!scope) {
				return null;
			}
			if (scope.hasOwnProperty('vm')) {
				return scope;
			} else {
				return getAppScope(scope.$parent);
			}
		}
		return {
			restrict: 'EA',
			scope: true,
			bindToController: {
				fsValidatorConfig: '=fsValidatorConfig',
				fsVerifyModel: '=fsVerifyModel',
				ngModel: '=ngModel',
				fsConfig: '=fsConfig'
			},
			require: ['fsGridLayout', '^^?form', '^^?fsTabContentTransclude'],
			template: $templateCache.get('fs/layout/template/fsGridLayout.html'),
			controller: ['$scope', function ($scope) {
				var ctrl = this;
				ctrl.isActive = function (grid) {
					var expression = grid ? grid.active : undefined;
					if (expression === undefined) {
						return true;
					}
					if (angular.isString(expression) && expression.indexOf('vm.') < 0) {
						return evalBooleanExpression(expression, $scope.ngModel);
					} else {
						return evalBooleanExpression(expression, $scope);
					}
				};

				ctrl.isShow = function (grid) {
					var expression = grid ? grid.show : undefined;
					if (grid.model == 'password') {
						grid;
					}
					if (expression === undefined) {
						return true;
					}
					if (angular.isString(expression) && expression.indexOf('vm.') < 0) {
						return evalBooleanExpression(expression, $scope.ngModel);
					} else {
						return evalBooleanExpression(expression, $scope);
					}
				};
				ctrl.isReadonly = function (grid) {
					var expression = grid ? grid.readonly : undefined;
					if (expression === undefined) {
						return false;
					}
					if (angular.isString(expression) && expression.indexOf('vm.') < 0) {
						return evalBooleanExpression(expression, $scope.ngModel);
					} else {
						return evalBooleanExpression(expression, $scope);
					}
				};
				ctrl.showStyle = function (grid) {
					var expression = grid ? grid.readonly : undefined;
					if (grid.showStyle) {
						return grid.showStyle;
					}
					if (expression === undefined) {
						return 'edit';
					}
					if (evalBooleanExpression(expression, $scope)) {
						if (/^view$/.test(grid.gridClass) || /\sview$/.test(grid.gridClass) || /\sview\s/.test(grid.gridClass)) {
							return 'view';
						}
						return 'readonly';
					} else {
						return 'edit';
					}
				};
			}],
			controllerAs: 'fsGridLayoutController',
			compile: function ($element, $attrs) {
				// Compilation
				$element.addClass('fsGridLayout');
				return {
					// Pre-link
					pre: function ($scope, $element, $attrs, ctrls, $transclude) {
						var watcher, fsVerifyModelWatch, watchers = [], fsGridLayoutController = ctrls[0], formCtrl = ctrls[1], fsTabCtrl = ctrls[2], template = '';
						var topFormCtrl = getTopFormCtrl(formCtrl);
						registerFormCtrls(topFormCtrl, formCtrl);
						$scope.$on('$destroy', function () {
							while (watchers.length) {
								(watchers.shift() || angular.noop)();
							}
						});
						fsVerifyModelWatch = $scope.$watch($attrs.fsVerifyModel, function (verifyModel) {
							if (fsGridLayoutController.compareResult) {
								fsVerifyModelWatch();
							}
							if (fsGridLayoutController.ngModel && fsGridLayoutController.ngModel.primaryKey && verifyModel && verifyModel.primaryKey && fsGridLayoutController.formContainer
								&& fsGridLayoutController.formContainer.length > 0) {
								fsGridLayoutController.compareResult = verifyCompare(fsGridLayoutController, fsGridLayoutController.formContainer, fsGridLayoutController.ngModel, verifyModel);
							}
						});
						watcher = $scope.$watch($attrs.ngModel, function (ngModel) {
							$scope.ngModel = ngModel;
							if (topFormCtrl) {
								topFormCtrl._originalNgModel = {};
								topFormCtrl._originalNgModelKey = $attrs.ngModel;
								angular.copy(ngModel, topFormCtrl._originalNgModel);
								$rootScope.initFormState($scope, $attrs.ngModel, topFormCtrl.$name);
								validateForm($scope, fsGridLayoutController, topFormCtrl, fsTabCtrl, formCtrl);
							}
							if (ngModel && ngModel.primaryKey && fsGridLayoutController.fsVerifyModel && fsGridLayoutController.fsVerifyModel.primaryKey && fsGridLayoutController.formContainer
								&& fsGridLayoutController.formContainer.length > 0 && !fsGridLayoutController.compareResult) {
								fsGridLayoutController.compareResult = verifyCompare(fsGridLayoutController, fsGridLayoutController.formContainer, ngModel, fsGridLayoutController.fsVerifyModel);
							}
						});
						watchers.push(watcher);
						watcher = $scope.$watch($attrs.fsConfig, function (fsConfig) {
							var formContainer = [];
							if (!fsConfig) {
								return;
							}
							fsConfig.appScope = getAppScope($scope);
							angular.forEach(fsConfig.gridGroups, function(group) {
								if (group) {
									delete group.rows;
								}
							});
							buildGridGroups(fsConfig.appScope, formContainer, fsConfig.gridGroups || {}, fsConfig.gridGroupConfigs || {}, fsConfig.gridConfigs || {}, 12, fsConfig.name || "__general__", fsGridLayoutController.fsValidatorConfig);
							fsGridLayoutController.fsConfig = fsConfig;
							fsGridLayoutController.formContainer = formContainer;
							fsConfig.prepared = true;
							if (topFormCtrl) {
								validateForm($scope, fsGridLayoutController, topFormCtrl, fsTabCtrl, formCtrl);
							}
							if (fsGridLayoutController.ngModel && fsGridLayoutController.ngModel.primaryKey && fsGridLayoutController.fsVerifyModel && fsGridLayoutController.fsVerifyModel.primaryKey && fsGridLayoutController.formContainer
								&& fsGridLayoutController.formContainer.length > 0 && !fsGridLayoutController.compareResult) {
								fsGridLayoutController.compareResult = verifyCompare(fsGridLayoutController, fsGridLayoutController.formContainer, fsGridLayoutController.ngModel,
									fsGridLayoutController.fsVerifyModel);
							}
						});
						watchers.push(watcher);
						if (topFormCtrl) {
							topFormCtrl.saveValidateMessage = topFormCtrl.saveValidateMessage || function (formCtrl, validateResult) {
								if (formCtrl) {
									formCtrl.validateErrors = formCtrl.validateErrors || {};
									formCtrl.validateErrors[validateResult.field + '.' + validateResult.rule] = validateResult.messages;
								}
							};
							topFormCtrl.removeValidateMessage = topFormCtrl.removeValidateMessage || function (formCtrl, validateResult) {
								if (formCtrl && formCtrl.validateErrors && formCtrl.validateErrors[validateResult.field + '.' + validateResult.rule]) {
									delete formCtrl.validateErrors[validateResult.field + '.' + validateResult.rule];
								}
							};
						}
					},
					// Post-link
					post: function ($scope, $element, $attrs, formCtrl, $transclude) {
					}
				};
			}
		};

		function evalBooleanExpression(expression, $scope) {
			if (expression === true || expression === false) {
				return expression;
			}
			return $parse(expression)($scope);
		}
		function initGroupCollapsed(groupContainer, $scope) {
			var expression, getter;
			if (groupContainer.type == 'anonymous') {
				groupContainer.collapsed = function () {
					return false;
				};
				return;
			}
			expression = groupContainer.isCollapsed;
			if (expression == undefined) {
				groupContainer.isCollapsed = groupContainer.type == 'search' ? true : false;
			} else {
				groupContainer.isCollapsed = $parse(expression)($scope);
			}
			groupContainer.collapsed = function () {
				return groupContainer.isCollapsed;
			};
			groupContainer.toggleGroupCollapsed = function () {
				groupContainer.isCollapsed = !groupContainer.isCollapsed;
			};
			if (groupContainer.type == 'search') {
				$scope.$parent.toggleSearchGroupCollapsed = function () {
					return groupContainer.toggleGroupCollapsed();
				};
				$scope.$parent.isSearchCollapsed = function () {
					return groupContainer.collapsed();
				};
			}

		}
		function buildGridGroups($scope, container, groups, groupConfig, gridConfig, gridWidth, tabName, fsValidatorConfig) {
			var anonymousGroup;
			angular.forEach(groups, function (group) {
				if (angular.isArray(group)) { // anonymous group, just contains grids
					anonymousGroup = {
						name: 'anonymous',
						type: 'anonymous',
						grids: group,
						appScope: $scope
					};
					buildGridGroups($scope, container, [anonymousGroup], groupConfig, gridConfig, gridWidth, tabName);
				} else if (!groupConfig[group.name] || groupConfig[group.name].type === 'anonymous') { // anonymous group
					angular.extend(group, groupConfig[group.name]);
					group.type = group.type || 'anonymous';
					group.rows = group.rows || [];
					group.appScope = $scope;
					initGroupCollapsed(group, $scope);
					container.push(group);
					buildGrids($scope, group.rows, group.grids, groupConfig, gridConfig, gridWidth, group, tabName, fsValidatorConfig);
					if (group.gridGroup) {
						group.gridGroups = [];
						buildGridGroups($scope, group.gridGroups, group.gridGroup, groupConfig, gridConfig, gridWidth, tabName);
						delete group.gridGroup;
					}
				} else { // normal group
					angular.extend(group, groupConfig[group.name]);
					group.type = group.type || 'normal';
					group.rows = group.rows || [];
					group.appScope = $scope;
					initGroupCollapsed(group, $scope);
					container.push(group);
					buildGrids($scope, group.rows, group.grids, groupConfig, gridConfig, gridWidth, group, tabName);
					if (group.gridGroup) {
						group.gridGroups = [];
						buildGridGroups($scope, group.gridGroups, group.gridGroup, groupConfig, gridConfig, gridWidth, tabName, fsValidatorConfig);
						delete group.gridGroup;
					}
				}
			});
		}
		function verifyCompare(layoutCtrl, groupContainers, ngModel, verifyModel) {
			var value, oldValue;
			var compareResult = {
				fields: {}
			};
			angular.forEach(groupContainers, function (groupContainer) {
				angular.forEach(groupContainer.rows, function (row) {
					angular.forEach(row, function (gridConfig) {
						if (gridConfig.filter) {
							value = $parse(gridConfig.model + ' | ' + gridConfig.filter)(ngModel);
							oldValue = $parse(gridConfig.model + ' | ' + gridConfig.filter)(verifyModel);
						} else {
							value = $parse(gridConfig.model)(ngModel);
							oldValue = $parse(gridConfig.model)(verifyModel);
						}

						if (!angular.equals(value, oldValue)) {
							compareResult.fields[gridConfig.model] = buildCompareResult($parse, $filter, formatService, layoutCtrl, gridConfig, value, oldValue);
						} else {
							delete compareResult.fields[gridConfig.model];
						}
					});
				});
				angular.merge(compareResult, verifyCompare(layoutCtrl, groupContainers.groups, ngModel, verifyModel));
			});
			return compareResult;
		}
		function setGridPosition(gridObj, gridNumber) {
			gridObj.size = (gridObj.size && gridObj.size > gridNumber) ? gridNumber : ((gridObj.size && gridObj.size > 0) ? gridObj.size : 12);
			if (gridObj.gridClass) {
				gridObj.gridClass += ' col-sm-' + gridObj.size;
			} else {
				gridObj.gridClass = 'col-sm-' + gridObj.size;
			}
			if (gridObj.showStyle == 'view') {
				gridObj.gridClass += ' view';
			}
		}
		function buildGrids($scope, rows, grids, groupConfig, gridConfig, gridNumber, group, tabName, fsValidatorConfig) {
			var i = 0, row, gridObj;
			angular.forEach(grids, function (value, key) {
				if (angular.isString(value)) {// arrange grid by grid.size
					gridObj = gridConfig[value];
					if (!gridObj) {
						log.debug('can not found ' + value + ' config.');
						return;
					}
					gridObj.appScope = $scope;
					gridObj.tabName = tabName;
					//add this code for group readonly.
					if (gridObj.readonly === undefined && group && groupConfig[group.name] && groupConfig[group.name].readonly != undefined) {
						gridObj.readonly = groupConfig[group.name].readonly;
					}
					if (gridObj.active === undefined && group && groupConfig[group.name] && groupConfig[group.name].active != undefined) {
						gridObj.active = groupConfig[group.name].active;
					}
					setGridPosition(gridObj, gridNumber);
					if (i == 0) {
						row = [];
						rows.push(row);
					}
					i += gridObj.size;
					if (i > gridNumber && row.length > 0) {
						row = [gridObj];
						rows.push(row);
						i = gridObj.size;
					} else {
						row.push(gridObj);
					}
					prepareGridRules($parse, gridObj, fsValidatorConfig);
				} else if (angular.isArray(value)) {// force create a new row
					if (row) {
						row = null;
					}
					i = 0;
					buildGrids($scope, rows, value, groupConfig, gridConfig, gridNumber, group, tabName, fsValidatorConfig);
				}
			});
		}
		function validateForm($scope, fsGridLayoutController, topFormCtrl, tabController, formCtrl) {
			var formEntity = fsGridLayoutController.ngModel, fsConfig = fsGridLayoutController.fsConfig, promise, promises = [], defered, log = $log.getInstance('validateForm');
			if (!fsConfig) {
				log.debug("validateForm cannot found the fsConfig!");
			}
			if (!tabController || !fsConfig || !fsConfig.prepared || !topFormCtrl.formValidation) {
				return;
			}
			defered = topFormCtrl.formValidation[tabController.getTabIndex()] ? topFormCtrl.formValidation[tabController.getTabIndex()].defered : $q.defer();
			if (!topFormCtrl.formValidation[tabController.getTabIndex()]
				|| topFormCtrl.formValidation[tabController.getTabIndex()].validated !== false
				|| !fsGridLayoutController.fsValidatorConfig) {
				topFormCtrl.formValidation[tabController.getTabIndex()] = null;
				topFormCtrl.tabLoaded[tabController.getTabIndex()] = false;
				defered.resolve();
				return;
			}
			topFormCtrl.formValidation[tabController.getTabIndex()] = null;
			angular.forEach(fsGridLayoutController.fsConfig.gridConfigs, function (gridConfig) {
				if (gridConfig.rules) {
					angular.forEach(gridConfig.rules.ruleInfos, function (rule) {
						var ruleName = angular.lowercase(rule.ruleName);
						if ("conditiondependent" != ruleName) {
							var d = $q.defer(), validateResult = {
								'field': gridConfig.model,
								'rule': rule.ruleName,
								'messages': null
							};
							rule.fieldLabel = gridConfig.label;
							rule.ngModel = formEntity;
							rule.operatedField = gridConfig.field || gridConfig.model;
							rule.modelType = gridConfig.type;
							promises.push(d.promise);
							$injector.get(ruleName).validate($parse(gridConfig.model)(formEntity), undefined, rule).then(function (e) {
								validateResult.invalid = false;
								topFormCtrl.removeValidateMessage(formCtrl, validateResult);
								d.resolve();
							}, function (e) {
								validateResult.invalid = true;
								validateResult.messages = e.error;
								topFormCtrl.saveValidateMessage(formCtrl, validateResult);
								d.resolve();
							});
						}
					});
				}
			});
			$q.all(promises).then(function (e) {
				topFormCtrl.formValidation[tabController.getTabIndex()] = null;
				topFormCtrl.tabLoaded[tabController.getTabIndex()] = false;
				defered.resolve();
			});
		}
	}

	// <fs-group ng-model="vm.company" fs-verify-model="vm.companyOld" fs-group-config="groupObj"/>
	fsGroup.$inject = ['$templateCache', '$compile'];
	function fsGroup($templateCache, $compile) {
		return {
			restrict: 'EA',
			scope: {},
			bindToController: {
				fsValidatorConfig: '=fsValidatorConfig',
				fsVerifyModel: '=fsVerifyModel',
				ngModel: '=ngModel',
				groupContainer: '=fsGroupConfig'
			},
			require: ['fsGroup', '^fsGridLayout'],
			controller: function () { },
			controllerAs: 'fsGroupController',
			//template : $templateCache.get('fs/layout/template/fsGroup.html'),
			compile: function ($element, $attrs) {
				$element.addClass('fsGroup');
				return {
					pre: function ($scope, $element, $attrs, ctrls, $transclude) {
						var fsGroupController = ctrls[0];
						var contentTemplate, contentEL, content, activeShowExpression = '', clickCallbackExpression = '', collapseIconExpression = '';
						if (fsGroupController.hasOwnProperty('active') && fsGroupController.hasOwnProperty('show')) {
							activeShowExpression = 'ng-show="fsGroupController.isActive() && fsGroupController.isShow()"';
						} else if (fsGroupController.hasOwnProperty('active')) {
							activeShowExpression = 'ng-show="fsGroupController.isActive()"';
						} else if (fsGroupController.hasOwnProperty('show')) {
							activeShowExpression = 'ng-show="fsGroupController.isShow()"';
						}
						if (!fsGroupController.groupContainer.disableCollapsed) {
							clickCallbackExpression = 'ng-click="fsGroupController.groupContainer.toggleGroupCollapsed()"';
							collapseIconExpression = '<i class="pull-right icon" ng-class="{\'icon-down\': !fsGroupController.groupContainer.collapsed(), \'icon-up\': fsGroupController.groupContainer.collapsed()}"></i>';
						}
						if (fsGroupController.groupContainer.type == 'anonymous') {
							contentTemplate = 'fs/layout/template/fsAnonymousGroup.html';
						} else if (fsGroupController.groupContainer.type == 'search') {
							contentTemplate = 'fs/layout/template/fsSearchGroup.html';
						} else {
							contentTemplate = 'fs/layout/template/fsNormalGroup.html';
						}
						fsGroupController.searchCallback = function () {
							debugger;
							fsGroupController.groupContainer.appScope.searchCallback();
						};
						fsGroupController.isActive = function (gridObj) {
							if (gridObj) return ctrls[1].isActive(gridObj);
							return ctrls[1].isActive(fsGroupController.groupContainer);
						};
						fsGroupController.isShow = function (gridObj) {
							if (gridObj) return ctrls[1].isShow(gridObj);
							return ctrls[1].isShow(fsGroupController.groupContainer);
						};
						contentEL = $templateCache.get(contentTemplate);
						contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
						contentEL = contentEL.replace(/CLICK_CALLBACK_EXPRESSION/g, clickCallbackExpression);
						contentEL = contentEL.replace(/COLLAPSE_ICON_EXPRESSION/g, collapseIconExpression);
						contentEL = angular.element(contentEL);
						content = $compile(contentEL)($scope);
						// Prevent jQuery cache memory leak (template is now redundant after linking)
						// contentEL.remove();
						$element.append(content);
					}
				};
			}
		};
	}

	//<fs-grid ng-model="vm.company" fs-verify-model="vm.companyOld" fs-grid-config="gridObj"/>
	fsGrid.$inject = ['$rootScope', '$parse', '$templateCache'];
	function fsGrid($rootScope, $parse, $templateCache) {
		return {
			restrict: 'EA',
			scope: {
				fsValidatorConfig: '=fsValidatorConfig',
				fsVerifyModel: '=fsVerifyModel',
				ngModel: '=ngModel',
				fsGridConfig: '=fsGridConfig',
				uiGrid: '=fsUiGrid',
				uiGridRow: '=fsUiGridRow',
				row: '=fsUiGridRow',
				uiGridCol: '=fsUiGridCol'
			},
			require: ['^^?form', '^^?uiGrid', '^^?fsGridLayout', '^^?fsAside', 'fsGrid', '^^?fsUiGridCompareCtrl'],
			template: $templateCache.get('fs/layout/template/fsGrid.html'),
			controller: ['$scope', function ($scope) {
				var ctrl = this;
				ctrl.validateResult = {};
				ctrl.getValidateMessages = function () {
					var validateMessages = [];
					if (ctrl.validateResult && ctrl.validateResult[$scope.fsGridConfig.model] && ctrl.showErrors()) {
						angular.forEach(ctrl.validateResult[$scope.fsGridConfig.model].messages, function (message, rule) {
							if (message) {
								validateMessages.push(message);
							}
						});
					}
					return validateMessages;
				};
				ctrl.getGridEntity = function () {
					return $scope.ngModel;
				};
				ctrl.getUiGridRow = function () {
					return $scope.uiGridRow;
				};
				ctrl.getGridAppScope = function () {
					return $scope.fsGridConfig.appScope;
				};
			}],
			controllerAs: 'fsGridController',
			compile: function ($element, $attrs) {
				$element.addClass('fsGrid');
				return {
					pre: function ($scope, $element, $attrs, ctrls, $transclude) {
						var initialized = false, objectEquality = false, modelWatched = false, ngModelWatched = false, topFormCtrl, formCtrl, uiGridCtrl, fsGridLayoutCtrl, fsGridCtrl, asideCtrl, fsUiGridCompareCtrl, originalNgModel, watcher, watchers = [];
						var content, contentEL;
						formCtrl = ctrls[0];
						uiGridCtrl = ctrls[1];
						fsGridLayoutCtrl = ctrls[2];
						asideCtrl = ctrls[3];
						fsGridCtrl = ctrls[4];
						fsUiGridCompareCtrl = ctrls[5];
						topFormCtrl = getTopFormCtrl(formCtrl);
						registerFormCtrls(topFormCtrl, formCtrl);
						if (topFormCtrl && uiGridCtrl && uiGridCtrl.grid && uiGridCtrl.grid.appScope && uiGridCtrl.grid.options && !topFormCtrl._originalNgModel) {
							originalNgModel = uiGridCtrl.grid.appScope.$eval(uiGridCtrl.grid.options.data);
							if (originalNgModel) {
								topFormCtrl._originalNgModel = [];
								topFormCtrl._originalNgModelKey = uiGridCtrl.grid.options.data;
								angular.copy(originalNgModel, topFormCtrl._originalNgModel);
								$rootScope.initFormState(uiGridCtrl.grid.appScope, uiGridCtrl.grid.options.data, topFormCtrl.$name);
							}
						}
						fsGridCtrl.isActive = function (grid) {
							var active, expression = grid ? grid.active : undefined;
							if (expression === undefined) {
								active = true;
							} else if ($scope.uiGridRow) {
								active = evalBooleanExpression(expression, $scope);
							} else {
								if (angular.isString(expression) && expression.indexOf('vm.') < 0) {
									active = evalBooleanExpression(expression, $scope.ngModel);
								} else {
									active = evalBooleanExpression(expression, $scope.fsGridConfig.appScope);
								}
							}
							if (active === false) {
								if (grid.model) {
									if (formCtrl && formCtrl[grid.model] && formCtrl[grid.model].$invalid && formCtrl.validateErrors && grid.rules && grid.rules.ruleInfos) {
										delete fsGridCtrl.validateResult[grid.model];
										angular.forEach(grid.rules.ruleInfos, function (rule) {
											delete formCtrl.validateErrors[grid.model + '.' + rule.ruleName];
										});
									}
									if ($scope.ngModel !== undefined && $scope.ngModel[grid.model] !== undefined) {
										if (grid.hasOwnProperty('defaultValue')) {
											$scope._hasSetValueFromDefaultValue = true;
											$scope.ngModel[grid.model] = grid.defaultValue;
										} else {
											delete $scope.ngModel[grid.model];
										}
									}
								}
							} else if ($scope._backupValue && $scope._backupValue.hasOwnProperty(grid.model)) {
								$scope._hasSetValueFromDefaultValue = false;
								$scope.ngModel[grid.model] = $scope._backupValue[grid.model];
							}
							return active;
						};
						fsGridCtrl.isShow = function (grid) {
							var expression = grid ? grid.show : undefined;
							if (expression === undefined) {
								return true;
							} else if ($scope.uiGridRow) {
								return evalBooleanExpression(expression, $scope);
							} else {
								if (angular.isString(expression) && expression.indexOf('vm.') < 0) {
									return evalBooleanExpression(expression, $scope.ngModel);
								} else {
									return evalBooleanExpression(expression, $scope.fsGridConfig.appScope);
								}
							}
						};
						fsGridCtrl.isReadonly = function (grid) {
							var expression = grid ? grid.readonly : undefined;
							if (expression === undefined) {
								return false;
							} else if ($scope.uiGridRow) {
								return evalBooleanExpression(expression, $scope);
							} else {
								if (angular.isString(expression) && expression.indexOf('vm.') < 0) {
									return evalBooleanExpression(expression, $scope.ngModel);
								} else {
									return evalBooleanExpression(expression, $scope.fsGridConfig.appScope);
								}
							}
						};
						$scope.$root.count1 = $scope.$root.count1 ? $scope.$root.count1 : 0;
						fsGridCtrl.showStyle = function (grid) {
							var expression = grid ? grid.readonly : undefined;
							if (grid.showStyle) {
								return grid.showStyle;
							}
							if (expression === undefined) {
								return 'edit';
							} else if ($scope.uiGridRow) {
								expression = evalBooleanExpression(expression, $scope);
							} else {
								expression = evalBooleanExpression(expression, $scope.fsGridConfig.appScope);
							}
							if (expression) {
								if (/^view\s/.test(grid.gridClass) || /\sview$/.test(grid.gridClass) || /\sview\s/.test(grid.gridClass)) {
									return 'view';
								}
								return 'readonly';
							} else {
								return 'edit';
							}
						};
						fsGridCtrl.focus = function () {
							if (asideCtrl) {
								asideCtrl.showHelp($scope.fsGridConfig.help);
							}
						};
						fsGridCtrl.blur = function () {
							if (asideCtrl) {
								asideCtrl.removeHelp();
							}
						};
						fsGridCtrl.isChanged = function () {
							if (uiGridCtrl) {
								if (!$scope.uiGridRow || !$scope.uiGridRow.compareResult || !$scope.uiGridRow.compareResult.fields[$scope.uiGridCol.colDef.model]) {
									return false;
								}
								return $scope.uiGridRow.compareResult.isCreated || $scope.uiGridRow.compareResult.isDeleted || $scope.uiGridRow.compareResult.fields[$scope.uiGridCol.colDef.model].hasChanged;
							} else if (fsGridLayoutCtrl) {
								if (!fsGridLayoutCtrl.compareResult || !$scope.fsGridConfig || !fsGridLayoutCtrl.compareResult.fields[$scope.fsGridConfig.model]) {
									return false;
								}
								return fsGridLayoutCtrl.compareResult.fields[$scope.fsGridConfig.model].hasChanged;
							}
						};
						fsGridCtrl.showErrors = function () {
							if ($scope.fsGridConfig && formCtrl && formCtrl[$scope.fsGridConfig.model] && formCtrl[$scope.fsGridConfig.model].$invalid && (formCtrl[$scope.fsGridConfig.model].$dirty || topFormCtrl.$submitted)) {
								return true;
							}
							return false;
						};
						fsGridCtrl.saveValidateMessage = function (validateResult) {
							fsGridCtrl.validateResult[$scope.fsGridConfig.model] = fsGridCtrl.validateResult[$scope.fsGridConfig.model] || {};
							fsGridCtrl.validateResult[$scope.fsGridConfig.model].messages = fsGridCtrl.validateResult[$scope.fsGridConfig.model].messages || {};
							fsGridCtrl.validateResult[$scope.fsGridConfig.model].messages[validateResult.rule] = validateResult.messages;
							if (formCtrl) {
								formCtrl.validateErrors = formCtrl.validateErrors || {};
								formCtrl.validateErrors[validateResult.field + '.' + validateResult.rule] = validateResult.messages;
							}
						};
						fsGridCtrl.removeValidateMessage = function (validateResult) {
							if (fsGridCtrl.validateResult && fsGridCtrl.validateResult[$scope.fsGridConfig.model] && fsGridCtrl.validateResult[$scope.fsGridConfig.model].messages
								&& fsGridCtrl.validateResult[$scope.fsGridConfig.model].messages[validateResult.rule]) {
								delete fsGridCtrl.validateResult[$scope.fsGridConfig.model].messages[validateResult.rule];
							}
							if (formCtrl && formCtrl.validateErrors && formCtrl.validateErrors[validateResult.field + '.' + validateResult.rule]) {
								delete formCtrl.validateErrors[validateResult.field + '.' + validateResult.rule];
							}
							if (topFormCtrl && topFormCtrl.validateErrors && topFormCtrl.validateErrors[validateResult.field + '.' + validateResult.rule]) {
								delete topFormCtrl.validateErrors[validateResult.field + '.' + validateResult.rule];
							}
						};
						fsGridCtrl.getTooltipMessage = function () {
							var tooltip, validateMessages = fsGridCtrl.getValidateMessages();
							if (validateMessages.length > 0) {
								tooltip = validateMessages;
							} else if (fsGridLayoutCtrl && fsGridLayoutCtrl.compareResult
								&& fsGridLayoutCtrl.compareResult.fields
								&& fsGridLayoutCtrl.compareResult.fields[$scope.fsGridConfig.model]
								&& fsGridLayoutCtrl.compareResult.fields[$scope.fsGridConfig.model].tooltip) {
								tooltip = fsGridLayoutCtrl.compareResult.fields[$scope.fsGridConfig.model].tooltip;
							} else if ($scope.uiGridRow && $scope.uiGridRow.compareResult
								&& $scope.uiGridRow.compareResult.fields
								&& $scope.uiGridRow.compareResult.fields[$scope.fsGridConfig.model]
								&& $scope.uiGridRow.compareResult.fields[$scope.fsGridConfig.model].tooltip) {
								tooltip = $scope.uiGridRow.compareResult.fields[$scope.fsGridConfig.model].tooltip;
							} else if ($scope.fsGridConfig.tooltip) {
								tooltip = $scope.fsGridConfig.tooltip;
							} else {
								tooltip = null;
							}
							return tooltip;
						};
						$scope.$on('$destroy', function () {
							while (watchers.length) {
								(watchers.shift() || angular.noop)();
							}
							destoryFormCtrls([topFormCtrl], formCtrl);
						});
						watcher = $scope.$watch('fsGridConfig', function (gridObj) {
							var callback;
							if (gridObj && gridObj.type) {
								if (uiGridCtrl && gridObj.label) {
									gridObj.showLabel = false;
								} else if (gridObj.label) {
									gridObj.showLabel = true;
								}
								$scope.fsGridConfig = gridObj;
								$scope.fsGridConfig.gridClass = $scope.fsGridConfig.gridClass || defaultGridClass;
								$scope.fsGridConfig.inputClass = $scope.fsGridConfig.inputClass || defaultInputClass;
								if ($scope.fsGridConfig.uppercase == undefined) {
									$scope.fsGridConfig.uppercase = true;
								}
								prepareGridTooltip($scope, fsGridCtrl);
								if (!$scope.fsGridConfig.hasPreparedGridRules) {
									prepareGridRules($parse, $scope.fsGridConfig, $scope.fsValidatorConfig);
								}
								if (!ngModelWatched) {
									ngModelWatched = true;
									if ($scope.fsGridConfig.dynamicParameter && $scope.fsGridConfig.defaultValue) {
										watchers.push($scope.$watch($parse('ngModel'), function (model, oldModel) {
											if ($scope.fsGridConfig.dynamicParameter && $scope.fsGridConfig.defaultValue) {
												var getter = $parse($scope.fsGridConfig.model);
												if (!getter($scope.ngModel)) {
													getter.assign($scope.ngModel, $scope.fsGridConfig.defaultValue);
												}
												prepareDynamicParameter($scope, ctrls[4]);
											}
										}));
									}
								}
								if (!modelWatched) {
									modelWatched = true;
									//callback = $scope.fsGridConfig.options ? $scope.fsGridConfig.options.callback : $scope.fsGridConfig.callback;
									//if (angular.isString($scope.fsGridConfig.options) || (callback && callback.changed)) {
									if ($scope.fsGridConfig.type == "date-range" || $scope.fsGridConfig.type == "datetime-range" || $scope.fsGridConfig.type == "number-range"
										|| $scope.fsGridConfig.type == "money-range" || $scope.fsGridConfig.type == "pencentage-range") {
										objectEquality = true;
									} else {
										objectEquality = false;
									}
									watchers.push($scope.$watch($parse('ngModel.' + $scope.fsGridConfig.model), function (model, oldModel) {
										var isInitialized = false, options = $scope.fsGridConfig.options;
										if (model == oldModel) {
											isInitialized = true;
										}
										if (angular.isString(options)) {
											options = ((uiGridCtrl && uiGridCtrl.grid) ? uiGridCtrl.grid.appScope : $scope.fsGridConfig.appScope).$eval(options);
										}
										callback = options ? options.callback : $scope.fsGridConfig.callback;
										if (callback && angular.isString(callback.changed)) {
											callback = ((uiGridCtrl && uiGridCtrl.grid) ? uiGridCtrl.grid.appScope : $scope.fsGridConfig.appScope).$eval(callback.changed);
										}
										if (callback && angular.isFunction(callback)) {
											if (!$scope.ngModel && $scope.uiGridRow) {
												callback($scope.uiGridRow.entity, formCtrl, isInitialized);
											} else {
												callback($scope.ngModel, formCtrl, isInitialized);
											}
										}
										prepareDynamicParameter($scope, ctrls[4]);
										//back up value for grid witch has active property.
										if ($scope._hasSetValueFromDefaultValue) return;
										if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.ngModel && $scope.ngModel.hasOwnProperty($scope.fsGridConfig.model)) {
											if (!$scope._backupValue) $scope._backupValue = {};
											$scope._backupValue[$scope.fsGridConfig.model] = model;
										}
									}, objectEquality));
									//}
									/*if ($scope.fsGridConfig.dynamicParameter) {
										watchers.push($scope.$watch($parse('ngModel.' + $scope.fsGridConfig.model), function(model) {
											prepareDynamicParameter($scope, ctrls[4]);
										}));
									}*/
								}
							}
						});
						watchers.push(watcher);
					},
					// Post-link
					post: function ($scope, $element, $attrs, ctrls, $transclude) {
						var initialized = false, objectEquality = false, modelWatched = false, ngModelWatched = false, formCtrl, topFormCtrl, uiGridCtrl, fsGridCtrl, watcher, watchers = [];
						formCtrl = ctrls[0];
						uiGridCtrl = ctrls[1];
						fsGridCtrl = ctrls[4];
						topFormCtrl = getTopFormCtrl(formCtrl);
						if ($scope.fsGridConfig) {
							$scope.fsGridConfig.attrName = $attrs.fsGridConfig;
							$scope.fsGridConfig.postPositionRequired = '';
						}
					}
				};
			}
		};

		function prepareGridTooltip($scope, fsGridCtrl) {
			if ($scope.fsGridConfig.tooltip) {
				$scope.fsGridConfig.tooltip = angular.isArray($scope.fsGridConfig.tooltip) ? $scope.fsGridConfig.tooltip : [$scope.fsGridConfig.tooltip];
			}
			fsGridCtrl.tooltip = $scope.fsGridConfig.tooltip;
			$scope.fsGridConfig.tooltipClass = $scope.fsGridConfig.tooltipClass || defaultTooltipClass;
			$scope.fsGridConfig.tooltipPlacement = $scope.fsGridConfig.tooltipPlacement || defaultTooltipPlacement;
			$scope.fsGridConfig.tooltipTemplateUrl = $scope.fsGridConfig.tooltipTemplateUrl || defaultTooltipTemplateUrl;
		}
		function prepareDynamicParameter($scope, fsGridCtrl) {
			var dynamicParam, ngModelStr;
			if ($scope.fsGridConfig.dynamicParameter) {
				$scope.fsGridConfig.model
				ngModelStr = $scope.fsGridConfig.model.replace(/.value/, "");
				dynamicParam = $scope.$eval('ngModel.' + ngModelStr);
				if (dynamicParam) {
					angular.merge(dynamicParam, $scope.fsGridConfig.dynamicParameter);
				}
			}
		}
		function evalBooleanExpression(expression, $scope) {
			if (expression === true || expression === false) {
				return expression;
			} else {
				return $parse(expression)($scope);
			}
		}
	}

	//<fs-color ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsColor.$inject = ['$templateCache', '$compile'];
	function fsColor($templateCache, $compile) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', 'fsColor'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var content, contentEL;
				var activeShowExpression = '', labelExpression = '', prefixExpression = '';
				initComponent(ctrls[1], ctrls[0]);
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				if ($scope.fsGridConfig.hasOwnProperty('prefix')) {
					prefixExpression = '<span class="input-group-addon">{{::fsGridConfig.prefix}}</span>';
				}
				contentEL = $templateCache.get('fs/layout/template/fsColor.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				if ($scope.fsGridConfig.filter) {
					contentEL = contentEL.replace(/MODEL_FILTER/g, '| ' + $scope.fsGridConfig.filter);
				} else {
					contentEL = contentEL.replace(/MODEL_FILTER/g, '');
				}
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = contentEL.replace(/PREFIX_EXPRESSION/g, prefixExpression);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	//<fs-label ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsLabel.$inject = ['$templateCache', '$compile'];
	function fsLabel($templateCache, $compile) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', 'fsLabel'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var contentEL, content, activeShowExpression = '';
				initComponent(ctrls[1], ctrls[0]);
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				contentEL = $templateCache.get('fs/layout/template/fsLabel.html');
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	//<fs-link ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsLink.$inject = ['$compile', '$templateCache'];
	function fsLink($compile, $templateCache) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', 'fsLink', '^^?form', '^^?uiGrid'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var content, contentEL, formCtrl = ctrls[2], uiGridCtrl = ctrls[3], filter;
				initComponent(ctrls[1], ctrls[0]);
				$scope.linkClick = function () {
					var callback;
					if (angular.isString($scope.fsGridConfig.options)) {
						$scope.fsGridConfig.options = ((uiGridCtrl && uiGridCtrl.grid) ? uiGridCtrl.grid.appScope : $scope).$eval($scope.fsGridConfig.options);
					}
					callback = $scope.fsGridConfig.options ? $scope.fsGridConfig.options.callback : $scope.fsGridConfig.callback;
					if (callback && angular.isString(callback.click)) {
						callback = ((uiGridCtrl && uiGridCtrl.grid) ? uiGridCtrl.grid.appScope : $scope.fsGridConfig.appScope).$eval(callback.click);
					}
					if (callback && angular.isFunction(callback)) {
						if (!$scope.ngModel && ($scope.uiGridRow)) {
							callback($scope.uiGridRow.entity, formCtrl);
						} else {
							callback($scope.ngModel, formCtrl);
						}
					}
				};
				if ($scope.fsGridConfig.filter) {
					filter = $scope.fsGridConfig.filter;
				} else {
					filter = 'translate';
				}
				var activeShowExpression = '', labelExpression = '';
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				contentEL = $templateCache.get('fs/layout/template/fsLink.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, '{{ ngModel.' + $scope.fsGridConfig.model + ' | ' + filter + ' }}');
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	//<fs-image ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsImage.$inject = ['$templateCache', '$compile'];
	function fsImage($templateCache, $compile) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', 'fsImage'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var content, contentEL;
				var activeShowExpression = '', labelExpression = '';
				initComponent(ctrls[1], ctrls[0]);
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				contentEL = $templateCache.get('fs/layout/template/fsImage.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	//<fs-text ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsText.$inject = ['$templateCache', '$compile', '$parse'];
	function fsText($templateCache, $compile, $parse) {
		function TextPosition(textElement, ngModelCtrl) {
			var self = this;
			self.textElement = textElement;
			self.start = 0;
			self.end = 0;
			self.insert = function (ngModel, fsGridConfig, pattern) {
				var getter = $parse(fsGridConfig.model), value = getter(ngModel) || '';
				var pre = value.substr(0, self.start);
				var post = value.substr(self.end);
				value = pre + pattern + post;
				getter.assign(ngModel, value);
			};
			self.savePosition = function () {
				if (typeof (self.textElement.selectionStart) == "number") {
					ff();
				} else if (document.selection) {
					ie();
				}
			};
			init();
			function init() {
				self.textElement.onkeydown = self.savePosition;
				self.textElement.onkeyup = self.savePosition;
				self.textElement.onmousedown = self.savePosition;
				self.textElement.onmouseup = self.savePosition;
				self.textElement.onfocus = self.savePosition;
			}
			function ff() {
				self.start = self.textElement.selectionStart;
				self.end = self.textElement.selectionEnd;
			}
			function ie() {
				var range = document.selection.createRange();
				if (range.parentElement().id == self.textElement.id) {
					var range_all = document.body.createTextRange();
					range_all.moveToElementText(self.textElement);
					for (self.start = 0; range_all.compareEndPoints("StartToStart", range) < 0; self.start++) {
						range_all.moveStart('character', 1);
					}
					for (var i = 0; i <= this.start; i++) {
						if (self.textElement.value.charAt(i) == '\n')
							self.start++;
					}
					var range_all = document.body.createTextRange();
					range_all.moveToElementText(self.textElement);
					for (self.end = 0; range_all.compareEndPoints('StartToEnd', range) < 0; self.end++) {
						range_all.moveStart('character', 1);
					}
					for (var i = 0; i <= self.end; i++) {
						if (self.textElement.value.charAt(i) == '\n')
							self.end++;
					}
				}
			}
		}
		return {
			restrict: 'EA',
			require: ['^fsGrid', 'fsText', '^^?uiGrid'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: ['$scope', '$element', function ($scope, $element) {
				var ctrl = this;
				ctrl.isPatternActive = function () {
					if (!$scope.fsGridConfig.patternOptions) {
						return false;
					}
					if (!$scope.fsGridConfig.patternOptions.active && $scope.fsGridConfig.patternOptions.active !== false) {
						return true;
					}
					return $parse($scope.fsGridConfig.patternOptions.active)($scope.ngModel) || $parse($scope.fsGridConfig.patternOptions.active)($scope)
				};
				ctrl.initTextPosition = function () {
					if (ctrl.isPatternActive()) {
						ctrl.textPosition = new TextPosition($element[0].querySelector('#textInput'));
					}
				};
				ctrl.insertPattern = function (pattern) {
					if (ctrl.textPosition) {
						ctrl.textPosition.insert($scope.ngModel, $scope.fsGridConfig, pattern);
					}
				};
			}],
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var watchers = [], watcher, content, contentEL;
				initComponent(ctrls[1], ctrls[0]);
				$scope.$on('$destroy', function () {
					(watcher || angular.noop)();
				});
				$scope.isInTable = function () {
					return ctrls[2] ? true : false;
				};
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionsListener);
					watchers.push(watcher);
				} else if ($scope.fsGridConfig.options) {
					if (angular.isString($scope.fsGridConfig.options.data)) {
						watcher = watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options.data), gridOptionDataListener);
						watchers.push(watcher);
					}
				}
				if (angular.isString($scope.fsGridConfig.patternOptions)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.patternOptions, gridPatternOptionsListener);
					watchers.push(watcher);
				} else if ($scope.fsGridConfig.patternOptions) {
					if (angular.isString($scope.fsGridConfig.patternOptions.data)) {
						watcher = watchGridScope(ctrls[0], $parse($scope.fsGridConfig.patternOptions.data), gridPatternOptionDataListener);
						watchers.push(watcher);
					}
				}
				function gridOptionsListener(options) {
					$scope.fsGridConfig.options = options;
				}
				function gridOptionDataListener(data) {
					$scope.fsGridConfig.options.data = data;
				}
				function gridPatternOptionsListener(patternOptions) {
					$scope.fsGridConfig.patternOptions = patternOptions;
				}
				function gridPatternOptionDataListener(data) {
					if ($scope.fsGridConfig.patternOptions) {
						$scope.fsGridConfig.patternOptions.data = data;
					}
				}

				var activeShowExpression = '', labelExpression = '', prefixExpression = '', patternExpression = '', patternContentExpression = '', requiredExpression = '';
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				if ($scope.fsGridConfig.hasOwnProperty('prefix')) {
					prefixExpression = '<span class="input-group-addon">{{::fsGridConfig.prefix}}</span>';
				}
				if (ctrls[1].isPatternActive()) {
					patternExpression = '<span class="input-group-addon" uib-dropdown-toggle><icon class="icon icon-down"/></span>';
					patternContentExpression = ''
						+ '<ul uib-dropdown-menu ng-init="formComponentCtrl.initTextPosition()" class="dropdown-menu" ng-class="{\'dropdown-append-body\':appendToBody()}">\n'
						+ '    <li role="menuitem" ng-repeat="item in fsGridConfig.patternOptions.data"><a href="javascript:void(0)" ng-click="formComponentCtrl.insertPattern(item.value)">{{item.label |translate}}</a></li>\n'
						+ '</ul>\n';
				}
				contentEL = $templateCache.get('fs/layout/template/fsText.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				if ($scope.fsGridConfig.filter) {
					contentEL = contentEL.replace(/MODEL_FILTER/g, '| ' + $scope.fsGridConfig.filter);
				} else {
					contentEL = contentEL.replace(/MODEL_FILTER/g, '');
				}
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = contentEL.replace(/PREFIX_EXPRESSION/g, prefixExpression);
				contentEL = contentEL.replace(/PATTERN_EXPRESSION/g, patternExpression);
				contentEL = contentEL.replace(/PATTERN_CONTENT_EXPRESSION/g, patternContentExpression);
				contentEL = contentEL.replace(/REQUIRED_EXPRESSION/g, $scope.fsGridConfig.postPositionRequired);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
				/*$timeout(function() {
					var input = content[0].querySelector('input');
					if (input) {
						input = angular.element(input);
						input.on('focus', function() {
							ctrls[0].focus();
						});
						input.on('blur', function() {
							ctrls[0].blur();
						});
					}
				}, 0, false);*/
			}
		};
	}

	//<fs-password ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsPassword.$inject = ['$templateCache', '$compile'];
	function fsPassword($templateCache, $compile) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', 'fsPassword'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var content, contentEL;
				var activeShowExpression = '', labelExpression = '', switchExpression = '', keyboardExpression = '', requiredExpression = '';
				initComponent(ctrls[1], ctrls[0]);
				$scope.isVisible = 'invisible';
				$scope.clickVisible = function () {
					if ($scope.isVisible == 'invisible') {
						$scope.isVisible = 'visible';
					} else {
						$scope.isVisible = 'invisible';
					}
				};
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				if ($scope.fsGridConfig.options && $scope.fsGridConfig.options.showSwitcher) {
					switchExpression = '<div class="input-group-addon" ng-click="clickVisible()" style="cursor:pointer;"><i class="icon iconfont icon-{{isVisible}}"></i></div>';
				}
				if ($scope.fsGridConfig.options && $scope.fsGridConfig.options.showKeyboard) {
					keyboardExpression = '<div class="input-group-addon" style="cursor: pointer;"><i class="icon iconfont icon-keyboard"></i></div>';
				}
				contentEL = $templateCache.get('fs/layout/template/fsPassword.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				if ($scope.fsGridConfig.filter) {
					contentEL = contentEL.replace(/MODEL_FILTER/g, '| ' + $scope.fsGridConfig.filter);
				} else {
					contentEL = contentEL.replace(/MODEL_FILTER/g, '| sensitive:0:0:true');
				}
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = contentEL.replace(/SWITCH_EXPRESSION/g, switchExpression);
				contentEL = contentEL.replace(/KEYBOARD_EXPRESSION/g, keyboardExpression);
				contentEL = contentEL.replace(/REQUIRED_EXPRESSION/g, $scope.fsGridConfig.postPositionRequired);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	//<fs-checkbox ng-model="vm.company"/>
	fsCheckbox.$inject = ['$templateCache', '$compile', '$parse'];
	function fsCheckbox($templateCache, $compile, $parse) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', '^^?form', 'fsCheckbox'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: ['$scope', function ($scope) {
				var getter = $parse($scope.fsGridConfig.model);
				$scope.indexOfModel = function (option) {
					var index = -1;
					if (!$scope.ngModel)
						return;
					angular.forEach(getter($scope.ngModel), function (value, key) {
						if (value == option) {
							index = key;
						}
					});
					return index;
				};
			}],
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var content, contentEL, watcher, watched, watchers = [], getter = $parse($scope.fsGridConfig.model), formCtrl = (ctrls.length == 2 ? ctrls[1] : undefined);
				initComponent(ctrls[2], ctrls[0]);
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionsListener);
					watchers.push(watcher);
				} else if ($scope.fsGridConfig.options) {
					if (angular.isString($scope.fsGridConfig.options.data)) {
						watcher = watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options.data), gridOptionDataListener);
						watchers.push(watcher);
					}
				}
				$scope.toggleSelection = function toggleSelection(option) {
					var list, getter = $parse($scope.fsGridConfig.model);
					if (!$scope.ngModel)
						return;
					if (formCtrl && $scope.fsGridConfig && formCtrl[$scope.fsGridConfig.model]) {
						formCtrl[$scope.fsGridConfig.model].$dirty = true;
					}
					list = getter($scope.ngModel) || [];
					var index = $scope.indexOfModel(option);
					if (index > -1) {
						list.splice(index, 1);
						if (list.length == 0) {
							getter.assign($scope.ngModel, null);
						} else {
							getter.assign($scope.ngModel, angular.copy(list));
						}
					} else {
						if ($scope.fsGridConfig.options.maxChoiceNum && list.length == $scope.fsGridConfig.options.maxChoiceNum) {
							list.push(option);
							list.shift();
							getter.assign($scope.ngModel, angular.copy(list));
						} else {
							list.push(option);
							getter.assign($scope.ngModel, angular.copy(list));
						}
					}
				};
				$scope.clicked = function () {
					if ($scope.fsGridConfig.options.callback) {
						if (angular.isString($scope.fsGridConfig.options.callback.clicked)) {
							var callback = $scope.fsGridConfig.appScope.$eval($scope.fsGridConfig.options.callback.clicked);
							if (!callback) {
								log.debug("cannot found the callback function:" + $scope.fsGridConfig.options.callback.clicked);
							} else {
								callback($scope.ngModel);
							}
						} else if (angular.isFunction($scope.fsGridConfig.options.callback.clicked)) {
							$scope.fsGridConfig.options.callback.clicked($scope.ngModel);
						}
					}
				};
				function gridOptionsListener(options) {
					var watcher;
					$scope.fsGridConfig.options = options;
					if (!watched && $scope.fsGridConfig.options && angular.isString($scope.fsGridConfig.options.data)) {
						watched = true;
						watcher = watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options.data), gridOptionDataListener);
						watchers.push(watcher);
					}
				}
				function gridOptionDataListener(optionData) {
					$parse('data').assign($scope.fsGridConfig.options, optionData);
				}

				var activeShowExpression = '', labelExpression = '', requiredExpression = '';
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				contentEL = $templateCache.get('fs/layout/template/fsCheckbox.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = contentEL.replace(/REQUIRED_EXPRESSION/g, $scope.fsGridConfig.postPositionRequired);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	//<fs-checkbox-group ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsCheckboxGroup.$inject = ['$templateCache', '$compile'];
	function fsCheckboxGroup($templateCache, $compile) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', '^^?form', 'fsCheckboxGroup'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: ['$scope', '$parse', function ($scope, $parse) {
				var getter = $parse($scope.fsGridConfig.model);
				$scope.indexOfModel = function (option) {
					var i, model = getter($scope.ngModel) || [];
					for (i = 0; i < model.length; i++) {
						if (model[i] == option) {
							return i;
						}
					}
					return -1;
				};
				$scope.toggleSelectionAll = function ($event, group, option) {
					var index, model;
					$event.stopPropagation();
					if (!$scope.ngModel)
						return;
					model = getter($scope.ngModel);
					if (!model) {
						getter.assign($scope.ngModel, []);
						model = getter($scope.ngModel);
					}
					if (option !== undefined) {
						index = $scope.indexOfModel(option);
						if (index > -1) {
							model.splice(index, 1);
							group.allChecked = false;
						} else {
							model.push(option);
							group.allChecked = allChecked(group.data);
						}
					} else {
						if (!group.allChecked) {
							angular.forEach(group.data, function (item) {
								if (model.length > 0) {
									index = $scope.indexOfModel(item.value);
									if (index > -1) {
										model.splice(index, 1);
									}
								}
							});
						} else {
							angular.forEach(group.data, function (item) {
								index = $scope.indexOfModel(item.value);
								if (index < 0) {
									model.push(item.value);
								}
							});
						}
					}
				};
				$scope.toggleGroupCollapse = function (group) {
					group.isCollapsed = !group.isCollapsed;
				};
				function allChecked(options) {
					var i, j, model;
					if (!$scope.ngModel)
						return false;
					model = getter($scope.ngModel) || [];
					options = options || [];
					if (model.length < options.length) {
						return false;
					}
					if ($scope.fsGridConfig.options.groups.length == 1 && model.length == options.length) {
						return true;
					}
					for (i = 0; i < options.length; i++) {
						for (j = 0; j < model.length; j++) {
							if (options[i].value == model[j]) {
								break;
							}
						}
						if (j >= model.length) {
							return false;
						}
					}
					return true;
				};
			}],
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var content, contentEL, watcher, watchers = [];
				initComponent(ctrls[2], ctrls[0]);
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionsListener);
					watchers.push(watcher);
				} else if ($scope.fsGridConfig.options) {
					angular.forEach($scope.fsGridConfig.options.groups, function (group) {
						if (group && angular.isString(group.data)) {
							watcher = watchGridScope(ctrls[0], $parse(group.data), gridOptionDataListener);
							watchers.push(watcher);
						}
					});
				}
				function gridOptionsListener(options) {
					var watcher;
					if (options) {
						$scope.fsGridConfig.options = options;
						angular.forEach($scope.fsGridConfig.options.groups, function (group) {
							if (group && !group.watched && angular.isString(group.data)) {
								group.watched = true;
								watcher = watchGridScope(ctrls[0], $parse(group.data), gridOptionDataListener);
								watchers.push(watcher);
							}
						});
					}
				}
				function gridOptionDataListener(groupData) {
					$parse('data').assign(group, groupData);
				}

				var activeShowExpression = '', labelExpression = '', requiredExpression = '';
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				contentEL = $templateCache.get('fs/layout/template/fsCheckboxGroup.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	//<fs-date ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsDate.$inject = ['$templateCache', '$compile', '$parse', 'commonService', '$q', '$injector'];
	function fsDate($templateCache, $compile, $parse, commonService, $q, $injector) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', 'fsDate'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controllerAs: 'formComponentCtrl',
			controller: ['$scope', 'storageService', function ($scope, storageService) {
				var self = this, formatInfo = storageService.getFormatInfo();
				$scope.fsGridConfig.placeholder = formatInfo[$scope.fsGridConfig.format] || $scope.fsGridConfig.format || formatInfo.defaultDate;
				self.altInputFormats = [formatInfo[$scope.fsGridConfig.format] || $scope.fsGridConfig.format || formatInfo.defaultDate];
				$scope._opened = false;
				$scope._dateOptions = {
					showWeeks: false,
					datepickerMode: $scope.fsGridConfig.options ? ($scope.fsGridConfig.options.mode || 'day') : 'day',
					maxMode: $scope.fsGridConfig.options ? ($scope.fsGridConfig.options.maxMode || 'year') : 'year',
					minMode: $scope.fsGridConfig.options ? ($scope.fsGridConfig.options.minMode || 'day') : 'day',
					monthColumns: 4,
					startingDay: 0,
					watchActiveDate: function (activeDate) {
						var defer = $q.defer();
						if ($scope.fsGridConfig.options && $scope.fsGridConfig.options.holidaySelectable) {
							return defer.promise;
						}
						$scope.activeDate = activeDate;
						var activeDateYear = $scope.activeDate ? $scope.activeDate.getFullYear() : new Date().getFullYear();
						var modelDateYear = $parse($scope.fsGridConfig.model)($scope.ngModel) ? $parse($scope.fsGridConfig.model)($scope.ngModel).getFullYear() : new Date().getFullYear();
						var calendarYear = (activeDateYear || modelDateYear);
						if (!calendarYear) {
							defer.reject();
							return defer.promise;
						}
						var params = {
							calendarYear: calendarYear,
							calendarCodes: $scope._dateOptions.calendarCodes
						};
						if (($scope.holidays && $scope.holidays[params.calendarYear]) || !params.calendarCodes) {
							defer.reject();
							return defer.promise;
						}
						commonService.getHolidays(params).then(function (days) {
							if (!$scope.holidays) $scope.holidays = {};
							angular.forEach(params.calendarCodes, function (calendarCode) {
								if (!$scope.holidays[params.calendarYear]) $scope.holidays[params.calendarYear] = {};
								$scope.holidays[params.calendarYear][calendarCode] = days;
							});
							defer.resolve();
						}, function (e) {
							log.debug(e);
						});
						return defer.promise;
					},
					getBizDate: function () {
						return storageService.getSiteInfo() ? storageService.getSiteInfo().bizDate : new Date();
					}
				};
				$scope.fsGridConfig.closeOnDateSelection = true;
				if ($scope.fsGridConfig.type == "datetime") {
					$scope._dateOptions.showTimebar = true;
					$scope._dateOptions.showTimebLabel = true;
					$scope.fsGridConfig.closeOnDateSelection = false;
				}
				if (!($scope.fsGridConfig && $scope.fsGridConfig.options && $scope.fsGridConfig.options.initDate)) {
					$scope._dateOptions.initDate = $scope._dateOptions.getBizDate();
				}
			}],
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var content, contentEL, watcher, watched, watchers = [], fsGridCtrl = ctrls[0];
				initComponent(ctrls[1], ctrls[0]);
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionsListener);
					watchers.push(watcher);
				} else if ($scope.fsGridConfig.options) {
					if (angular.isString($scope.fsGridConfig.options.startingDay)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.startingDay, gridOptionStartingDayListener);
						watchers.push(watcher);
					}
					if (angular.isString($scope.fsGridConfig.options.dateDisabled)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.dateDisabled, gridOptionDateDisabledListener);
						watchers.push(watcher);
					} else {
						gridOptionDateDisabledListener($scope.fsGridConfig.options.dateDisabled);
					}
					if (angular.isString($scope.fsGridConfig.options.calendarCodes)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.calendarCodes, gridOptionCalendarCodesListener);
						watchers.push(watcher);
					} else {
						gridOptionCalendarCodesListener($scope.fsGridConfig.options.calendarCodes);
					}
					if (angular.isString($scope.fsGridConfig.options.maxDate)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.maxDate, gridOptionMaxDateListener);
						watchers.push(watcher);
					}
					if (angular.isString($scope.fsGridConfig.options.minDate)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.minDate, gridOptionMinDateListener);
						watchers.push(watcher);
					}
					if (angular.isString($scope.fsGridConfig.options.initDate)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.initDate, gridOptionInitDateListener);
						watchers.push(watcher);
					}
				} else if (!$scope.fsGridConfig.options) {
					$scope.fsGridConfig.options = {};
				}
				watcher = $scope.$watch($parse('ngModel.' + $scope.fsGridConfig.model), function (date) {
					if (angular.isDate(date)) {
						date.setHours(0);
						date.setMinutes(0);
						date.setSeconds(0);
						date.setMilliseconds(0);
					}
				});
				watchers.push(watcher);
				watcher = $scope.$watch(function () {
					if ($scope.fsGridConfig.options && $scope.fsGridConfig.options.holidaySelectable) {
						return false;
					}
					var activeDateYear = $scope.activeDate ? $scope.activeDate.getFullYear() : new Date().getFullYear();
					var modelDateYear = $parse($scope.fsGridConfig.model)($scope.ngModel) ? $parse($scope.fsGridConfig.model)($scope.ngModel).getFullYear() : new Date().getFullYear();
					var calendarYear = (modelDateYear || activeDateYear);
					if (!calendarYear)
						return;
					return {
						calendarYear: calendarYear,
						calendarCodes: $scope._dateOptions.calendarCodes
					};
				}, function (params, oldParams) {
					if ($scope.fsGridConfig.options && !$scope.fsGridConfig.options.dateDisabled && params && params.calendarCodes && params.calendarCodes.length > 0 && (!$scope.holidays || !angular.equals(params, oldParams))) {
						if ($scope.holidays && $scope.holidays[params.calendarYear]) {
							return;
						}
						commonService.getHolidays(params).then(function (days) {
							if (!$scope.holidays) $scope.holidays = {};
							angular.forEach(params.calendarCodes, function (calendarCode) {
								if (!$scope.holidays[params.calendarYear]) $scope.holidays[params.calendarYear] = {};
								$scope.holidays[params.calendarYear][calendarCode] = days;
							});
							//holidaycheck
							var gridConfig = $scope.fsGridConfig;
							if (gridConfig.rules) {
								angular.forEach(gridConfig.rules.ruleInfos, function (rule) {
									var ruleName = angular.lowercase(rule.ruleName);
									if ("holidaycheck" == ruleName) {
										var d = $q.defer(), validateResult = {
											'field': gridConfig.model,
											'rule': rule.ruleName,
											'messages': null
										};
										rule.fieldLabel = gridConfig.label;
										rule.ngModel = ctrls[0].getGridEntity();
										rule.operatedField = gridConfig.field || gridConfig.model;
										rule.modelType = gridConfig.type;
										rule.$scope = $scope;
										$injector.get(ruleName).validate($parse(gridConfig.model)(rule.ngModel), undefined, rule).then(function (e) {
											validateResult.invalid = false;
											fsGridCtrl.removeValidateMessage(validateResult);
											rule.fieldCtrl.$setValidity(rule.ruleName, true);
											d.resolve();
										}, function (e) {
											validateResult.invalid = true;
											validateResult.messages = e.error;
											fsGridCtrl.saveValidateMessage(validateResult);
											rule.fieldCtrl.$setValidity(rule.ruleName, false);
											d.reject();
										});
									}
								});
							}
						}, function (e) {
							log.debug(e);
							$scope.holidays = {};
						});
					}
				}, true);
				watchers.push(watcher);
				function gridOptionsListener(options) {
					var watcher;
					$scope.fsGridConfig.options = options;
					if (!watched && $scope.fsGridConfig.options) {
						watched = true;
						if (angular.isString($scope.fsGridConfig.options.startingDay)) {
							watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.startingDay, gridOptionStartingDayListener);
							watchers.push(watcher);
						}
						if (angular.isString($scope.fsGridConfig.options.dateDisabled)) {
							watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.dateDisabled, gridOptionDateDisabledListener);
							watchers.push(watcher);
						} else {
							gridOptionDateDisabledListener($scope.fsGridConfig.options.dateDisabled);
						}
						if (angular.isString($scope.fsGridConfig.options.calendarCodes)) {
							watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.calendarCodes, gridOptionCalendarCodesListener);
							watchers.push(watcher);
						} else {
							gridOptionCalendarCodesListener($scope.fsGridConfig.options.calendarCodes);
						}
						if (angular.isString($scope.fsGridConfig.options.maxDate)) {
							watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.maxDate, gridOptionMaxDateListener);
							watchers.push(watcher);
						}
						if (angular.isString($scope.fsGridConfig.options.minDate)) {
							watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.minDate, gridOptionMinDateListener);
							watchers.push(watcher);
						}
						if (angular.isString($scope.fsGridConfig.options.initDate)) {
							watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.initDate, gridOptionInitDateListener);
							watchers.push(watcher);
						}
					}
				}
				function gridOptionStartingDayListener(startingDay) {
					$scope._dateOptions.startingDay = startingDay || 0;
				}
				function gridOptionDateDisabledListener(disableFunction) {
					$scope._dateOptions.dateDisabled = disableFunction;
				}
				function gridOptionCalendarCodesListener(calendarCodes) {
					$scope._dateOptions.calendarCodes = calendarCodes;
					if (!$scope._dateOptions.dateDisabled) {
						$scope._dateOptions.dateDisabled = function (dateModel) {
							if (dateModel.mode === 'day') {
								var holidaysArray = [], year = dateModel.date.getFullYear();
								if (calendarCodes && $scope.holidays && $scope.holidays[year]) {
									angular.forEach(calendarCodes, function (calendarCode) {
										holidaysArray = holidaysArray.concat($scope.holidays[year][calendarCode]);
									});
								}
								return commonService.isHoliday(dateModel.date, holidaysArray);
							} else {
								return false;
							}
						};
					}
				}
				function gridOptionMaxDateListener(maxDate) {
					$scope._dateOptions.maxDate = maxDate;
				}
				function gridOptionMinDateListener(minDate) {
					$scope._dateOptions.minDate = minDate;
				}
				function gridOptionInitDateListener(initDate) {
					$scope._dateOptions.initDate = initDate;
				}

				var activeShowExpression = '', labelExpression = '', requiredExpression = '';
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				contentEL = $templateCache.get('fs/layout/template/fsDate.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				if ($scope.fsGridConfig.filter) {
					contentEL = contentEL.replace(/MODEL_FILTER/g, '| ' + $scope.fsGridConfig.filter);
				} else {
					contentEL = contentEL.replace(/MODEL_FILTER/g, '| dateFormat:\'' + ($scope.fsGridConfig.format ? $scope.fsGridConfig.format : 'date') + '\'');
				}
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = contentEL.replace(/REQUIRED_EXPRESSION/g, $scope.fsGridConfig.postPositionRequired);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	// <fs-date-range ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsDateRange.$inject = ['$templateCache', '$compile', '$parse', 'commonService', '$q', '$injector'];
	function fsDateRange($templateCache, $compile, $parse, commonService, $q, $injector) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', 'fsDateRange'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controllerAs: 'formComponentCtrl',
			controller: ['$scope', 'storageService', function ($scope, storageService) {
				var self = this, formatInfo = storageService.getFormatInfo();
				$scope.fsGridConfig.placeholder = formatInfo[$scope.fsGridConfig.format] || $scope.fsGridConfig.format || formatInfo.defaultDate;
				self.altInputFormats = [formatInfo[$scope.fsGridConfig.format] || $scope.fsGridConfig.format || formatInfo.defaultDate];
				$scope._opened = false;
				$scope._fromDateOptions = {
					showWeeks: false,
					datepickerMode: $scope.fsGridConfig.options ? ($scope.fsGridConfig.options.mode || 'day') : 'day',
					maxMode: $scope.fsGridConfig.options ? ($scope.fsGridConfig.options.maxMode || 'year') : 'year',
					minMode: $scope.fsGridConfig.options ? ($scope.fsGridConfig.options.minMode || 'day') : 'day',
					monthColumns: 4,
					startingDay: 0,
					watchActiveDate: function (activeDate) {
						var defer = $q.defer();
						if ($scope.fsGridConfig.options && $scope.fsGridConfig.options.holidaySelectable) {
							return defer.promise;
						}
						$scope.activeDate = activeDate;
						var fromDateYear, toDateYear, activeDateYear;
						var range = $parse($scope.fsGridConfig.model)($scope.ngModel);
						fromDateYear = (range && range.from && angular.isDate(range.from)) ? range.from.getFullYear() : undefined;
						toDateYear = (range && range.to && angular.isDate(range.to)) ? range.to.getFullYear() : undefined;
						activeDateYear = $scope.activeDate ? $scope.activeDate.getFullYear() : new Date().getFullYear();
						var params = {
							activeDateYear: activeDateYear,
							fromDateYear: fromDateYear,
							toDateYear: toDateYear,
							calendarCodes: $scope._fromDateOptions.calendarCodes
						};
						var years = [];
						if (params.activeDateYear) {
							years.push(params.activeDateYear)
						}
						if (params.fromDateYear && params.fromDateYear != params.activeDateYear) {
							years.push(params.fromDateYear)
						}
						if (params.toDateYear && params.fromDateYear != params.toDateYear && params.toDateYear != params.activeDateYear) {
							years.push(params.toDateYear)
						}
						angular.forEach(years, function (year) {
							params.calendarYear = year;
							if (($scope.holidays && $scope.holidays[params.calendarYear]) || !params.calendarCodes) {
								defer.reject();
								return defer.promise;
							}
							commonService.getHolidays(params).then(function (days) {
								if (!$scope.holidays) $scope.holidays = {};
								angular.forEach(params.calendarCodes, function (calendarCode) {
									if (!$scope.holidays[params.calendarYear]) $scope.holidays[params.calendarYear] = {};
									$scope.holidays[params.calendarYear][calendarCode] = days;
								});
								defer.resolve();
							}, function (e) {
								log.debug(e);
							});
						});
						return defer.promise;
					},
					getBizDate: function () {
						return storageService.getSiteInfo() ? storageService.getSiteInfo().bizDate : new Date();
					}
				};
				$scope._toDateOptions = {
					showWeeks: false,
					datepickerMode: $scope.fsGridConfig.options ? ($scope.fsGridConfig.options.mode || 'day') : 'day',
					maxMode: $scope.fsGridConfig.options ? ($scope.fsGridConfig.options.maxMode || 'year') : 'year',
					minMode: $scope.fsGridConfig.options ? ($scope.fsGridConfig.options.minMode || 'day') : 'day',
					monthColumns: 4,
					startingDay: 0,
					watchActiveDate: function (activeDate) {
						var defer = $q.defer();
						if ($scope.fsGridConfig.options && $scope.fsGridConfig.options.holidaySelectable) {
							return defer.promise;
						}
						$scope.activeDate = activeDate;
						var fromDateYear, toDateYear, activeDateYear;
						var range = $parse($scope.fsGridConfig.model)($scope.ngModel);
						fromDateYear = (range && range.from && angular.isDate(range.from)) ? range.from.getFullYear() : undefined;
						toDateYear = (range && range.to && angular.isDate(range.to)) ? range.to.getFullYear() : undefined;
						activeDateYear = $scope.activeDate ? $scope.activeDate.getFullYear() : new Date().getFullYear();
						var params = {
							activeDateYear: activeDateYear,
							fromDateYear: fromDateYear,
							toDateYear: toDateYear,
							calendarCodes: $scope._toDateOptions.calendarCodes
						};
						var years = [];
						if (params.activeDateYear) {
							years.push(params.activeDateYear)
						}
						if (params.fromDateYear && params.fromDateYear != params.activeDateYear) {
							years.push(params.fromDateYear)
						}
						if (params.toDateYear && params.fromDateYear != params.toDateYear && params.toDateYear != params.activeDateYear) {
							years.push(params.toDateYear)
						}
						angular.forEach(years, function (year) {
							params.calendarYear = year;
							if (($scope.holidays && $scope.holidays[params.calendarYear]) || !params.calendarCodes) {
								defer.reject();
								return defer.promise;
							}
							commonService.getHolidays(params).then(function (days) {
								if (!$scope.holidays) $scope.holidays = {};
								angular.forEach(params.calendarCodes, function (calendarCode) {
									if (!$scope.holidays[params.calendarYear]) $scope.holidays[params.calendarYear] = {};
									$scope.holidays[params.calendarYear][calendarCode] = days;
								});
								defer.resolve();
							}, function (e) {
								log.debug(e);
							});
						});
						return defer.promise;
					},
					getBizDate: function () {
						return storageService.getSiteInfo() ? storageService.getSiteInfo().bizDate : new Date();
					}
				};
				$scope.fsGridConfig.closeOnDateSelection = false;
				if ($scope.fsGridConfig.type == "datetime-range") {
					$scope._fromDateOptions.showTimebar = true;
					$scope._fromDateOptions.showTimebLabel = true;
					$scope._toDateOptions.showTimebar = true;
					$scope._toDateOptions.showTimebLabel = true;
				}
				if (!($scope.fsGridConfig && $scope.fsGridConfig.options && $scope.fsGridConfig.options.initDate)) {
					$scope._fromDateOptions.initDate = $scope._fromDateOptions.getBizDate();
					$scope._toDateOptions.initDate = $scope._toDateOptions.getBizDate();
				}
			}],
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var contentEL, content, watcher, watched, watchers = [], fsGridCtrl = ctrls[0];
				initComponent(ctrls[1], ctrls[0]);
				$scope.setDirty = function () {
					var gridConfig = $scope.fsGridConfig;
					if (gridConfig.rules && gridConfig.rules.ruleInfos && gridConfig.rules.ruleInfos.length > 0) {
						if (gridConfig.rules.ruleInfos[0].fieldCtrl) gridConfig.rules.ruleInfos[0].fieldCtrl.$setDirty(true);
						if (gridConfig.rules.ruleInfos[0].fieldCtrl2) gridConfig.rules.ruleInfos[0].fieldCtrl2.$setDirty(true);
					}
				}
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionsListener);
					watchers.push(watcher);
				} else if ($scope.fsGridConfig.options) {
					if (angular.isString($scope.fsGridConfig.options.startingDay)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.startingDay, gridOptionStartingDayListener);
						watchers.push(watcher);
					}
					if (angular.isString($scope.fsGridConfig.options.dateDisabled)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.dateDisabled, gridOptionDateDisabledListener);
						watchers.push(watcher);
					} else {
						gridOptionDateDisabledListener($scope.fsGridConfig.options.dateDisabled);
					}
					if (angular.isString($scope.fsGridConfig.options.calendarCodes)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.calendarCodes, gridOptionCalendarCodesListener);
						watchers.push(watcher);
					} else {
						gridOptionCalendarCodesListener($scope.fsGridConfig.options.calendarCodes);
					}
					if (angular.isString($scope.fsGridConfig.options.maxDate)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.maxDate, gridOptionMaxDateListener);
						watchers.push(watcher);
					}
					if (angular.isString($scope.fsGridConfig.options.minDate)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.minDate, gridOptionMinDateListener);
						watchers.push(watcher);
					}
					if (angular.isString($scope.fsGridConfig.options.initDate)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.initDate, gridOptionInitDateListener);
						watchers.push(watcher);
					}
				} else if (!$scope.fsGridConfig.options) {
					$scope.fsGridConfig.options = {};
				}
				watcher = $scope.$watch($parse('ngModel.' + $scope.fsGridConfig.model), function (range) {
					if (!range) {
						$parse('ngModel.' + $scope.fsGridConfig.model).assign($scope, {});
						return;
					}
					if ($scope.fsGridConfig.type == "date-range" && range && angular.isDate(range.from)) {
						range.from.setHours(0);
						range.from.setMinutes(0);
						range.from.setSeconds(0);
						range.from.setMilliseconds(0);
					}
					if ($scope.fsGridConfig.type == "date-range" && range && angular.isDate(range.to)) {
						range.to.setHours(0);
						range.to.setMinutes(0);
						range.to.setSeconds(0);
						range.to.setMilliseconds(0);
					}
					$scope.fsGridConfig.options = $scope.fsGridConfig.options || {};
					$scope.fsGridConfig.options.fromMaxDate = range.to || $scope.fsGridConfig.options.maxDate;
					$scope.fsGridConfig.options.toMinDate = range.from || $scope.fsGridConfig.options.minDate;
				});
				watchers.push(watcher);
				watcher = $scope.$watch(function () {
					if ($scope.fsGridConfig.options && $scope.fsGridConfig.options.holidaySelectable) {
						return false;
					}
					var fromDateYear, toDateYear, activeFromDateYear, activeToDateYear;
					var range = $parse($scope.fsGridConfig.model)($scope.ngModel);
					fromDateYear = (range && range.from && angular.isDate(range.from)) ? range.from.getFullYear() : undefined;
					toDateYear = (range && range.to && angular.isDate(range.to)) ? range.to.getFullYear() : undefined;
					activeFromDateYear = $scope.activeFromDate ? $scope.activeFromDate.getFullYear() : new Date().getFullYear();
					activeToDateYear = $scope.activeToDate ? $scope.activeToDate.getFullYear() : new Date().getFullYear();
					return {
						activeFromDateYear: activeFromDateYear,
						activeToDateYear: activeToDateYear,
						fromDateYear: fromDateYear,
						toDateYear: toDateYear,
						holidaySelectable: $scope.fsGridConfig.options.holidaySelectable,
						calendarCodes: $scope._fromDateOptions.calendarCodes
					};
				}, watchCalendarHoliday, true);
				watchers.push(watcher);
				function watchCalendarHoliday(params, oldParams) {
					if ($scope.fsGridConfig.options && !$scope.fsGridConfig.options.dateDisabled && params && (!$scope.holidays || !angular.equals(params, oldParams))) {
						var years = [];
						if (params.activeFromDateYear) {
							years.push(params.activeFromDateYear)
						}
						if (params.activeToDateYear) {
							years.push(params.activeToDateYear)
						}
						if (params.fromDateYear && params.fromDateYear != params.activeDateYear) {
							years.push(params.fromDateYear)
						}
						if (params.toDateYear && params.fromDateYear != params.toDateYear && params.toDateYear != params.activeDateYear) {
							years.push(params.toDateYear)
						}
						angular.forEach(years, function (year) {
							params.calendarYear = year;
							if ($scope.fsGridConfig.options && !$scope.fsGridConfig.options.dateDisabled && params && params.calendarCodes && params.calendarCodes.length > 0 && (!$scope.holidays || !angular.equals(params, oldParams))) {
								if (($scope.holidays && $scope.holidays[params.calendarYear])) {
									return;
								}
								commonService.getHolidays(params).then(function (days) {
									if (!$scope.holidays) $scope.holidays = {};
									angular.forEach(params.calendarCodes, function (calendarCode) {
										if (!$scope.holidays[params.calendarYear]) $scope.holidays[params.calendarYear] = {};
										$scope.holidays[params.calendarYear][calendarCode] = days;
									});
									if ($scope.fsGridConfig.rules) {
										angular.forEach($scope.fsGridConfig.rules.ruleInfos, function (rule) {
											var ruleName = angular.lowercase(rule.ruleName);
											if ("holidaycheck" == ruleName) {
												var d = $q.defer(), validateResult = {
													'field': $scope.fsGridConfig.model,
													'rule': rule.ruleName,
													'messages': null
												};
												rule.fieldLabel = $scope.fsGridConfig.label;
												rule.ngModel = ctrls[0].getGridEntity();
												rule.operatedField = $scope.fsGridConfig.field || $scope.fsGridConfig.model;
												rule.modelType = $scope.fsGridConfig.type;
												rule.$scope = $scope;
												$injector.get(ruleName).validate($parse($scope.fsGridConfig.model)(rule.ngModel), undefined, rule).then(function (e) {
													validateResult.invalid = false;
													fsGridCtrl.removeValidateMessage(validateResult);
													rule.fieldCtrl.$setValidity(rule.ruleName, true);
													d.resolve();
												}, function (e) {
													validateResult.invalid = true;
													validateResult.messages = e.error;
													fsGridCtrl.saveValidateMessage(validateResult);
													rule.fieldCtrl.$setValidity(rule.ruleName, false);
													d.reject();
												});
											}
										});
									}
								}, function (e) {
									log.debug(e);
									$scope.holidays = {};
								});
							}
						});
					}
				}
				function gridOptionsListener(options) {
					var watcher;
					$scope.fsGridConfig.options = options;
					if (!watched && $scope.fsGridConfig.options) {
						watched = true;
						if (angular.isString($scope.fsGridConfig.options.startingDay)) {
							watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.startingDay, gridOptionStartingDayListener);
							watchers.push(watcher);
						}
						if (angular.isString($scope.fsGridConfig.options.dateDisabled)) {
							watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.dateDisabled, gridOptionDateDisabledListener);
							watchers.push(watcher);
						} else {
							gridOptionDateDisabledListener($scope.fsGridConfig.options.dateDisabled);
						}
						if (angular.isString($scope.fsGridConfig.options.maxDate)) {
							watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.maxDate, gridOptionMaxDateListener);
							watchers.push(watcher);
						}
						if (angular.isString($scope.fsGridConfig.options.minDate)) {
							watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.minDate, gridOptionMinDateListener);
							watchers.push(watcher);
						}
						if (angular.isString($scope.fsGridConfig.options.initDate)) {
							watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.initDate, gridOptionInitDateListener);
							watchers.push(watcher);
						}
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.ccy, gridOptionCcyListener);
						watchers.push(watcher);
					}
				}
				function gridOptionStartingDayListener(startingDay) {
					$scope._fromDateOptions.startingDay = startingDay || 0;
					$scope._toDateOptions.startingDay = startingDay || 0;
				}
				function gridOptionDateDisabledListener(disableFunction) {
					$scope._fromDateOptions.dateDisabled = disableFunction;
					$scope._toDateOptions.dateDisabled = disableFunction;
				}
				function gridOptionMaxDateListener(maxDate) {
					$scope._fromDateOptions.maxDate = maxDate;
					$scope._toDateOptions.maxDate = maxDate;
				}
				function gridOptionMinDateListener(minDate) {
					$scope._fromDateOptions.minDate = minDate;
					$scope._toDateOptions.minDate = minDate;
				}
				function gridOptionInitDateListener(initDate) {
					$scope._fromDateOptions.initDate = initDate;
					$scope._toDateOptions.initDate = initDate;
				}
				function gridOptionCalendarCodesListener(calendarCodes) {
					$scope._fromDateOptions.calendarCodes = calendarCodes;
					$scope._toDateOptions.calendarCodes = calendarCodes;
					if (!$scope._fromDateOptions.dateDisabled) {
						$scope._fromDateOptions.dateDisabled = function (dateModel) {
							if (dateModel.mode === 'day') {
								var holidaysArray = [], year = dateModel.date.getFullYear();
								if (calendarCodes && $scope.holidays && $scope.holidays[year]) {
									angular.forEach(calendarCodes, function (calendarCode) {
										holidaysArray = holidaysArray.concat($scope.holidays[year][calendarCode]);
									});
								}
								return commonService.isHoliday(dateModel.date, holidaysArray);
							} else {
								return false;
							}
						};
					}
				}
				if (!$scope._toDateOptions.dateDisabled) {
					$scope._toDateOptions.dateDisabled = $scope._fromDateOptions.dateDisabled;
				}

				var activeShowExpression = '', labelExpression = '', requiredExpression = '';
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				contentEL = $templateCache.get('fs/layout/template/fsDateRange.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				if ($scope.fsGridConfig.filter) {
					contentEL = contentEL.replace(/MODEL_FILTER/g, '| ' + $scope.fsGridConfig.filter);
				} else {
					contentEL = contentEL.replace(/MODEL_FILTER/g, '| dateFormat:\'' + ($scope.fsGridConfig.format ? $scope.fsGridConfig.format : 'date') + '\'');
				}
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = contentEL.replace(/REQUIRED_EXPRESSION/g, $scope.fsGridConfig.postPositionRequired);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	//<fs-time ng-model="vm.company.cuttoffTime" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsTime.$inject = ['$templateCache', '$compile', '$parse'];
	function fsTime($templateCache, $compile, $parse) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', 'fsTime'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var content, contentEL, watcher, watchers = [];
				initComponent(ctrls[1], ctrls[0]);
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionsListener);
					watchers.push(watcher);
				} else if (!$scope.fsGridConfig.options) {
					$scope.fsGridConfig.options = { 'showSeconds': true };
				} else if ($scope.fsGridConfig.options.showSeconds == undefined) {
					$scope.fsGridConfig.options.showSeconds = true;
				}
				watcher = $scope.$watch($parse('ngModel.' + $scope.fsGridConfig.model), function (date) {
					if (angular.isDate(date)) {
						date.setYear(1970);
						date.setMonth(0);
						date.setDate(1);
						if (!$scope.fsGridConfig.options || !$scope.fsGridConfig.options.showSeconds) {
							date.setSeconds(0);
						}
						date.setMilliseconds(0);
					}
				});
				watchers.push(watcher);
				function gridOptionsListener(options) {
					$scope.fsGridConfig.options = options;
					if ($scope.fsGridConfig.options && $scope.fsGridConfig.options.showSeconds == undefined) {
						$scope.fsGridConfig.options.showSeconds = true;
					}
				}

				var activeShowExpression = '', labelExpression = '', requiredExpression = '';
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				contentEL = $templateCache.get('fs/layout/template/fsTime.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				if ($scope.fsGridConfig.filter) {
					contentEL = contentEL.replace(/MODEL_FILTER/g, '| ' + $scope.fsGridConfig.filter);
				} else {
					contentEL = contentEL.replace(/MODEL_FILTER/g, '| dateFormat:\'' + ($scope.fsGridConfig.format ? $scope.fsGridConfig.format : 'date') + '\'');
				}
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = contentEL.replace(/REQUIRED_EXPRESSION/g, $scope.fsGridConfig.postPositionRequired);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	//<fs-calendar-day ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsCalendarDay.$inject = ['$templateCache', '$compile', '$parse'];
	function fsCalendarDay($templateCache, $compile, $parse) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', 'fsCalendarDay'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var content, contentEL, i, holidayGetter, holidayList, watcher, watchers = [];
				$scope._calendarOptions = [];
				holidayGetter = $parse('ngModel.' + $scope.fsGridConfig.model);
				initComponent(ctrls[1], ctrls[0]);
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if ($scope.fsGridConfig.options && angular.isString($scope.fsGridConfig.options.calendarYear)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.calendarYear, function (calendarYear) {
						buildCalendarOptions(calendarYear);
					});
					watchers.push(watcher);
				} else if ($scope.fsGridConfig.options) {
					buildCalendarOptions($scope.fsGridConfig.options.calendarYear);
				}
				function buildCalendarOptions(calendarYear) {
					if (!calendarYear || !angular.isNumber(calendarYear)) {
						return;
					}
					for (i = 0; i < 12; i++) {
						$scope._calendarOptions[i] = {
							datepickerMode: 'day',
							startingDay: 0,
							showWeeks: false,
							initDate: new Date(calendarYear, i, 1, 0, 0, 0, 0),
							customClass: customClass,
							toggleHoliday: toggleHoliday,
							readonly: ctrls[1].isReadonly($scope.fsGridConfig)
						};
					}
				}
				function customClass(dt) {
					var holiday;
					dt.isHoliday = false;
					holidayList = holidayGetter($scope) || [];
					for (i = 0; i < holidayList.length; i++) {
						holiday = holidayList[i];
						if (holiday && angular.isDate(holiday) && holiday.getYear() == dt.date.getYear() && holiday.getMonth() == dt.date.getMonth()
							&& holiday.getDate() == dt.date.getDate()) {
							dt.isHoliday = true;
							return "btn-holiday";
						}
					}
					return "";
				}
				function toggleHoliday(dt) {
					var holiday;
					if ($scope.view) {
						return;
					}
					holidayList = holidayGetter($scope) || [];
					if (!dt.isHoliday) {
						holidayList.push(dt.date);
					} else {
						for (i = 0; i < holidayList.length; i++) {
							holiday = holidayList[i];
							if (holiday && holiday.getYear() == dt.date.getYear() && holiday.getMonth() == dt.date.getMonth()
								&& holiday.getDate() == dt.date.getDate()) {
								holidayList.splice(i, 1);
								break;
							}
						}
					}
					holidayGetter.assign($scope, holidayList);
				}
				contentEL = $templateCache.get('fs/layout/template/fsCalendarDay.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};

	}

	//<fs-radio ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsRadio.$inject = ['$templateCache', '$compile', '$parse'];
	function fsRadio($templateCache, $compile, $parse) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', 'fsRadio'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var content, contentEL, watcher, watched, watchers = [];
				initComponent(ctrls[1], ctrls[0]);
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionsListener);
					watchers.push(watcher);
				} else if ($scope.fsGridConfig.options && angular.isString($scope.fsGridConfig.options.data)) {
					var optionsDataExpresstion = $scope.fsGridConfig.options.data;
					$scope.fsGridConfig.options.data = [];
					watcher = watchGridScope(ctrls[0], optionsDataExpresstion, gridOptionDataListener);
					watchers.push(watcher);
				}
				function gridOptionsListener(options) {
					var watcher;
					$scope.fsGridConfig.options = options;
					if (!watched && $scope.fsGridConfig.options && angular.isString($scope.fsGridConfig.options.data)) {
						watched = true;
						var optionsDataExpresstion = $scope.fsGridConfig.options.data;
						$scope.fsGridConfig.options.data = [];
						watcher = watchGridScope(ctrls[0], optionsDataExpresstion, gridOptionDataListener);
						watchers.push(watcher);
					}
				}
				function gridOptionDataListener(data) {
					$parse('data').assign($scope.fsGridConfig.options, data);
				}

				var activeShowExpression = '', labelExpression = '', requiredExpression = '';
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				contentEL = $templateCache.get('fs/layout/template/fsRadio.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = contentEL.replace(/REQUIRED_EXPRESSION/g, $scope.fsGridConfig.postPositionRequired);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	// <fs-radio-card ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsRadioCard.$inject = ['$templateCache', '$compile', '$parse'];
	function fsRadioCard($templateCache, $compile, $parse) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', 'fsRadioCard'],
			scope: {
				ngModel: '=ngModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var contentEL, content, watcher, watched, watchers = [], radioCardCtrl = ctrls[1], getter;
				getter = $parse('ngModel.' + $scope.fsGridConfig.model + "_CardOffset");
				radioCardCtrl.cardOffset = getter($scope) || 0;
				initComponent(ctrls[1], ctrls[0]);
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				/**
				 * add this method to storage the CardOffset in the user queue page the cardOffset can be save in the searchstorage.
				 */
				function setCardOffset(cardOffset) {
					var getter = $parse('ngModel.' + $scope.fsGridConfig.model + "_CardOffset");
					getter.assign($scope, cardOffset);
				}
				radioCardCtrl.left = function () {
					radioCardCtrl.cardOffset--;
					setCardOffset(radioCardCtrl.cardOffset);
				};
				radioCardCtrl.right = function () {
					radioCardCtrl.cardOffset++;
					setCardOffset(radioCardCtrl.cardOffset);
				};
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionsListener);
					watchers.push(watcher);
				} else if ($scope.fsGridConfig.options && angular.isString($scope.fsGridConfig.options.data)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.data, gridOptionDataListener);
					watchers.push(watcher);
				} else {
					buildCardConfigs();
				}

				watcher = watchGridScope(ctrls[0], getShowCardCount, refreshCard);
				watchers.push(watcher);

				function gridOptionsListener(options) {
					var watcher;
					$scope.fsGridConfig.options = options;
					if (!watched && $scope.fsGridConfig.options && angular.isString($scope.fsGridConfig.options.data)) {
						watched = true;
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.data, gridOptionDataListener);
						watchers.push(watcher);
					} else {
						buildCardConfigs();
					}
				}
				function gridOptionDataListener(data) {
					$parse('data').assign($scope.fsGridConfig.options, data);
					buildCardConfigs();
				}
				function buildCardConfigs() {
					if ($scope.fsGridConfig.options.data && radioCardCtrl.cardOffset >= $scope.fsGridConfig.options.data.length) {
						radioCardCtrl.cardOffset = 0;
						setCardOffset(radioCardCtrl.cardOffset);
					}
					$scope.fsGridConfig.cardConfigs = [];
					angular.forEach($scope.fsGridConfig.options.data, function (data, index) {
						var config = {
							"name": $scope.fsGridConfig.name + "" + index,
							"value": data.value,
							"type": "card",
							"template": $scope.fsGridConfig.template,
							"size": 2,
							"options": {
								"data": {
									"icon": 'icon-status-' + data.value.toLowerCase(),
									"size": data.extProperty,
									"description": data.label
								}
							}
						}
						$scope.fsGridConfig.cardConfigs.push(config);
					});
				}
				function getShowCardCount() {
					var count = 0, body = $element[0].querySelector('.media-body');
					if (body && body.offsetWidth) {
						count = Math.floor(body.offsetWidth / 200);
					}
					return count;
				}

				function refreshCard(newCount, oldCount) {
					radioCardCtrl.showCardCount = newCount;
				}

				var activeShowExpression = '';
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				contentEL = $templateCache.get('fs/layout/template/fsRadioCard.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				// contentEL.remove();
				$element.append(content);
			}
		};
	}

	//<fs-textarea ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsTextarea.$inject = ['$templateCache', '$compile'];
	function fsTextarea($templateCache, $compile) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', 'fsTextarea'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var content, contentEL, watcher, watchers = [];
				initComponent(ctrls[1], ctrls[0]);
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionsListener);
					watchers.push(watcher);
				}
				function gridOptionsListener(options) {
					$scope.fsGridConfig.options = options;
				}

				var activeShowExpression = '', labelExpression = '', requiredExpression = '';
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				contentEL = $templateCache.get('fs/layout/template/fsTextarea.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				if ($scope.fsGridConfig.filter) {
					contentEL = contentEL.replace(/MODEL_FILTER/g, '| ' + $scope.fsGridConfig.filter);
				} else {
					contentEL = contentEL.replace(/MODEL_FILTER/g, '');
				}
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = contentEL.replace(/REQUIRED_EXPRESSION/g, $scope.fsGridConfig.postPositionRequired);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	// <fs-customer-select ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsCustomerSelect.$inject = ['$templateCache', '$compile', '$parse', '$timeout', '$log', '$filter'];
	function fsCustomerSelect($templateCache, $compile, $parse, $timeout, $log, $filter) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', 'fsCustomerSelect', '^^?uiGrid'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var contentEL, content, watcher, watched, watchers = [], gridOptionsData;
				var log = $log.getInstance('fsCustomerSelect');
				initComponent(ctrls[1], ctrls[0]);
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				$scope.appendToBody = function () {
					return ctrls[2] ? true : false;
				};
				if ($scope.fsGridConfig.template == 'company') {
					$scope.fsGridConfig.autoClose = 'always';
					$scope.fsGridConfig.options.scrollable = false;
				} else {
					$scope.fsGridConfig.autoClose = 'always';
				}
				if ($scope.fsGridConfig && $scope.fsGridConfig.options && $scope.fsGridConfig.options.filter) {
					if (angular.isString($scope.fsGridConfig.options.filter)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.filter, function (filterConfig) {
							filter.appScope = $scope.fsGridConfig.appScope;
							$scope.fsGridConfig.options.filter = filter;
						});
						watchers.push(watcher);
					} else {
						$scope.fsGridConfig.options.filter.appScope = $scope.fsGridConfig.appScope;
					}
				} else {
					var defaultFilter = {
						"model": $scope.fsGridConfig.model + "KeyWord",
						"uppercase": false,
						"type": "text",
						"options": {
							"callback": {
								"changed": function (obj) {
									var dataBackup = $scope.backupData;
									var newData = [];
									var keyWord = obj[$scope.fsGridConfig.model + "KeyWord"];
									if (obj && (keyWord || keyWord == 0)) {
										for (var i = 0; i < dataBackup.length; i++) {
											if (angular.lowercase($filter('translate')(dataBackup[i].label)).indexOf(angular.lowercase(keyWord)) != -1) {
												newData.push(dataBackup[i]);
											}
										}
										buildSelectOptions(ctrls[1].selectOption, newData);
										//$parse('data').assign($scope.fsGridConfig.options, newData);
									} else {
										buildSelectOptions(ctrls[1].selectOption, dataBackup);
										//$parse('data').assign($scope.fsGridConfig.options, dataBackup);
									}
								}
							}
						}
					};
					$scope.fsGridConfig.options.filter = defaultFilter;
					$scope.fsGridConfig.options.filter.appScope = $scope.fsGridConfig.appScope;
				}
				watchers.push($scope.$watchCollection('isOpen', function (isOpen) {
					var scroller, selectedEl;
					if (isOpen) {
						$timeout(function () {
							scroller = $element[0].querySelector('.scroller');
							if (scroller) {
								selectedEl = scroller.querySelector('.selected');
								if (selectedEl) {
									selectedEl.focus();
								}
							}
						}, 200);
					}
				}));
				ctrls[1].select = function (selectedItem, isFinal) {
					$scope.selectedItem = selectedItem;
					if ($scope.fsGridConfig.autoClose == 'always' || isFinal) {
						$parse($scope.fsGridConfig.model).assign($scope.ngModel, selectedItem.value);
					}
				};
				ctrls[1].selectOption = {
					'optionLevel': 1
				};
				/*watchers.push($scope.$watchCollection('fsGridConfig.options.data', function(data) {
					buildSelectOptions(ctrls[1].selectOption, data);
				}));*/
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionsListener);
					watchers.push(watcher);
				} else if ($scope.fsGridConfig.options) {
					$scope.fsGridConfig.options.scrollable = ($scope.fsGridConfig.template == 'normal' && $scope.fsGridConfig.options.scrollable == undefined) ? true : $scope.fsGridConfig.options.scrollable;
					$scope.fsGridConfig.options.hasEmptyOption = ($scope.fsGridConfig.options.hasEmptyOption == undefined) ? true : $scope.fsGridConfig.options.hasEmptyOption;
					if ($scope.fsGridConfig.options.data) {
						if (angular.isString($scope.fsGridConfig.options.data)) {
							gridOptionsData = $scope.fsGridConfig.appScope.$eval($scope.fsGridConfig.options.data)
							if (angular.isFunction(gridOptionsData)) {
								watcher = watchGridScope(ctrls[0], function () { return gridOptionsData($scope.ngModel); }, gridOptionDataListener);
							} else {
								watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.data, gridOptionDataListener);
							}
							watchers.push(watcher);
						} else {
							gridOptionsData = $scope.fsGridConfig.options.data;
							gridOptionDataListener(gridOptionsData);
						}
					}
				}
				function gridOptionsListener(options) {
					if (options) {
						$scope.fsGridConfig.options = options;
						$scope.fsGridConfig.options.scrollable = ($scope.fsGridConfig.template == 'normal' && $scope.fsGridConfig.options.scrollable == undefined) ? true : $scope.fsGridConfig.options.scrollable;
						$scope.fsGridConfig.options.hasEmptyOption = ($scope.fsGridConfig.options.hasEmptyOption == undefined) ? true : $scope.fsGridConfig.options.hasEmptyOption;
						if (options.data) {
							if (!watched && angular.isString(options.data)) {
								watched = true;
								gridOptionsData = $scope.fsGridConfig.appScope.$eval($scope.fsGridConfig.options.data)
								if (angular.isFunction(gridOptionsData)) {
									watchers.push(watchGridScope(ctrls[0], function () { return gridOptionsData($scope.ngModel); }, gridOptionDataListener));
								} else {
									watchers.push(watchGridScope(ctrls[0], options.data, gridOptionDataListener));
								}
							} else {
								gridOptionsData = $scope.fsGridConfig.options.data;
								gridOptionDataListener(gridOptionsData);
							}
						}
					}
				}
				function gridOptionDataListener(data) {
					//$parse('data').assign($scope.fsGridConfig.options, data);
					buildSelectOptions(ctrls[1].selectOption, data);
					$scope.backupData = angular.copy(data);
					//when the data length > 5 the default filter function is effecive.
					if ($scope.backupData && $scope.backupData.length > 5) {
						$scope.fsGridConfig.options.filterable = true;
					} else {
						$scope.fsGridConfig.options.filterable = false;
					}
				}
				function buildSelectOptions(selectOption, data) {
					if (!data) {
						return;
					}
					if ($scope.fsGridConfig.options.hasEmptyOption) {
						selectOption.data = [{
							"label": "",
							"value": ""
						}];
					} else {
						selectOption.data = [];
					}
					angular.forEach(data, function (menuData) {
						var config = {
							label: menuData.label,
							value: menuData.value
						};
						if (menuData.subItems && menuData.subItems.length > 0) {
							config.subSelectOption = { 'optionLevel': selectOption.optionLevel + 1 };
							buildSelectOptions(config.subSelectOption, menuData.subItems);
						}
						selectOption.data.push(config);
					});
				}

				var activeShowExpression = '', labelExpression = '', requiredExpression = '', customerOptionsExpression = '';
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				if ($scope.fsGridConfig.options) {
					if ($scope.fsGridConfig.options.scrollable) {
						customerOptionsExpression = ''
							+ '<div ng-if="fsGridConfig.options.scrollable" class="animated fadeInUp" uib-dropdown-menu>'
							+ '    <div fs-grid ng-if="fsGridConfig.options.filterable" ng-model="ngModel" fs-grid-config="fsGridConfig.options.filter" fs-validator="fsGridConfig.rules" style="padding:3px 5px;"></div>\n'
							+ '    <fs-scrollbar class="scroller" suppress-scroll-x="true" wheel-propagation="true" refresh-on-change="fsGridConfig.data">\n'
							+ '        <ul class="dropdown-menu" fs-customer-select-item ng-model="ngModel" fs-grid-config="fsGridConfig" fs-item-config="formComponentCtrl.selectOption" style="margin:0;">'
							+ '        </ul>\n'
							+ '    </fs-scrollbar>\n'
							+ '</div>\n';
					} else {
						customerOptionsExpression = ''
							+ '<ul ng-if="!fsGridConfig.options.scrollable" class="animated fadeInUp" ng-class="{\'dropdown-append-body\':appendToBody()}" uib-dropdown-menu '
							+ '  fs-customer-select-item ng-model="ngModel" fs-grid-config="fsGridConfig" fs-item-config="formComponentCtrl.selectOption" style="min-width:200px">\n'
							+ '</ul>\n';
					}
				}

				contentEL = $templateCache.get('fs/layout/template/fsCustomerSelect.html');
				contentEL = contentEL.replace(/VIEW_MODEL_NAME/g, $scope.fsGridConfig.model);
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = contentEL.replace(/REQUIRED_EXPRESSION/g, $scope.fsGridConfig.postPositionRequired);
				contentEL = contentEL.replace(/CUSTOMER_OPTIONS_EXPRESSION/g, customerOptionsExpression);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				//TODO: contentEL.remove();
				$element.append(content);
			}
		};
	}

	// <fs-customer-select-item fs-grid-config="fsGridConfig"/>
	fsCustomerSelectItem.$inject = ['$templateCache', '$compile', '$parse', '$log'];
	function fsCustomerSelectItem($templateCache, $compile, $parse, $log) {
		function getSelectedItem(modelValue, treeConfig, opens) {
			var i, selectedItem;
			if (!treeConfig.data || treeConfig.data.length == 0) {
				return selectedItem;
			}
			for (i = 0; i < treeConfig.data.length; i++) {
				if (angular.equals(modelValue, treeConfig.data[i].value)) {
					opens[treeConfig.optionLevel] = modelValue != treeConfig.data[i].value && treeConfig.data[i].label;
					selectedItem = treeConfig.data[i];
					return selectedItem;
				}
			}
			for (i = 0; i < treeConfig.data.length; i++) {
				if (treeConfig.data[i].subSelectOption) {
					selectedItem = getSelectedItem(modelValue, treeConfig.data[i].subSelectOption, opens);
					if (selectedItem != null && selectedItem != undefined) {
						opens[treeConfig.optionLevel] = modelValue != treeConfig.data[i].value && treeConfig.data[i].label;
						return selectedItem;
					}
				}
			}
		}
		return {
			restrict: 'EA',
			require: ['^uibDropdown', 'fsCustomerSelectItem', '^^fsCustomerSelect'],
			scope: {
				ngModel: '=ngModel',
				fsGridConfig: '=fsGridConfig',
				fsItemConfig: '=fsItemConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var contentEL, content, watcher, watchers = [], opens = [];
				var log = $log.getInstance('fsCustomerSelectItem');
				$scope.customerSelect = ctrls[2];
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if ($scope.fsItemConfig.optionLevel == 1) {
					$scope.filterConfig = $scope.fsGridConfig.options.filter;
				}
				ctrls[1].isOpen = function (selectOption, item) {
					if (!item.subSelectOption || !opens[selectOption.optionLevel]) {
						return false;
					}
					return opens[selectOption.optionLevel] == item.label;
				};
				ctrls[1].clickItem = function (selectOption, item) {
					if (item) {
						$parse($scope.fsGridConfig.model + 'ViewModel').assign($scope.customerSelect, item);
						if ($scope.fsGridConfig.autoClose != 'outsideClick') {
							$parse($scope.fsGridConfig.model).assign($scope.ngModel, item.value);
						}
					}
				};
				ctrls[1].mouseover = function (selectOption, item) {
					opens[selectOption.optionLevel] = item.label;
				};
				if ($scope.fsItemConfig.optionLevel == 1) {
					watcher = $scope.$watch('ngModel.' + $scope.fsGridConfig.model, function (model) {
						var selectItem = $parse($scope.fsGridConfig.model + 'ViewModel')($scope.customerSelect);
						if ((model != null && model != undefined)) {
							selectItem = getSelectedItem(model, $scope.fsItemConfig, opens);
							if (selectItem != null && selectItem != undefined) {
								$parse($scope.fsGridConfig.model + 'ViewModel').assign($scope.customerSelect, selectItem);
							} else {
								$parse('ngModel.' + $scope.fsGridConfig.model).assign($scope, null);
							}
						}
					});
					watchers.push(watcher);
				}
				watcher = $scope.$watch(function () {
					return ctrls[0].isOpen();
				}, function (isOpen) {
					var selectItem = $parse($scope.fsGridConfig.model + 'ViewModel')($scope.customerSelect);
					if (!isOpen && selectItem) {
						opens = [];
						getSelectedItem(selectItem.value, $scope.fsItemConfig, opens);
					}
				});
				watchers.push(watcher);
				contentEL = $templateCache.get('fs/layout/template/fsCustomerSelectItem.html');
				if ($scope.filterConfig) {
					contentEL = contentEL.replace(/FILTER_MODEL/g, 'ngModel.' + $scope.filterConfig.model);
				}
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	//<fs-select ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsSelect.$inject = ['$templateCache', '$compile', '$log'];
	function fsSelect($templateCache, $compile, $log) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', 'fsSelect'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var contentEL, content, watcher, watched, watchers = [], gridOptionsData;
				var log = $log.getInstance('fsSelect');
				initComponent(ctrls[1], ctrls[0]);
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				$scope.options = [];
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionsListener);
					watchers.push(watcher);
				} else if ($scope.fsGridConfig.options) {
					$scope.fsGridConfig.options.hasEmptyOption = ($scope.fsGridConfig.options.hasEmptyOption == undefined) ? true : $scope.fsGridConfig.options.hasEmptyOption;
					if (angular.isString($scope.fsGridConfig.options.data)) {
						gridOptionsData = $scope.fsGridConfig.appScope.$eval($scope.fsGridConfig.options.data)
						if (angular.isFunction(gridOptionsData)) {
							watcher = watchGridScope(ctrls[0], function () { return gridOptionsData($scope.ngModel); }, gridOptionDataListener);
						} else {
							watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.data, gridOptionDataListener);
						}
						watchers.push(watcher);
					} else {
						$scope.options = $scope.fsGridConfig.options.data;
					}
				}
				function gridOptionsListener(options) {
					if (!options) {
						return;
					}
					$scope.fsGridConfig.options = options;
					if ($scope.fsGridConfig.options) $scope.fsGridConfig.options.hasEmptyOption = ($scope.fsGridConfig.options.hasEmptyOption == undefined) ? true : $scope.fsGridConfig.options.hasEmptyOption;
					if (!watched && $scope.fsGridConfig.options && angular.isString($scope.fsGridConfig.options.data)) {
						watched = true;
						gridOptionsData = $scope.fsGridConfig.appScope.$eval($scope.fsGridConfig.options.data)
						if (angular.isFunction(gridOptionsData)) {
							watchers.push(watchGridScope(ctrls[0], function () { return gridOptionsData($scope.ngModel); }, gridOptionDataListener));
						} else {
							watchers.push(watchGridScope(ctrls[0], $scope.fsGridConfig.options.data, gridOptionDataListener));
						}
					} else {
						$scope.options = $scope.fsGridConfig.options.data;
					}
				}
				function gridOptionDataListener(data) {
					if (data && data.length > 0) {
						if (data[0].value == '' || data[0].value == null) {
							data = data.slice(1);
							log.debug("model name " + $scope.fsGridConfig.model + " of dropdown must not have empty option in the option data, please modify the code.");
						}
					}
					$scope.options = data;
					//$parse('data').assign($scope.fsGridConfig.options, data);
				}

				var activeShowExpression = '', labelExpression = '', requiredExpression = '';
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				contentEL = $templateCache.get('fs/layout/template/fsSelect.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = contentEL.replace(/REQUIRED_EXPRESSION/g, $scope.fsGridConfig.postPositionRequired);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	//<fs-multiple-select ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsMultipleSelect.$inject = ['$templateCache', '$compile', '$parse'];
	function fsMultipleSelect($templateCache, $compile, $parse) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', 'fsMultipleSelect'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var content, contentEL, watcher, watched, watchers = [];
				initComponent(ctrls[1], ctrls[0]);
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				var getter = $parse($scope.fsGridConfig.model);
				$scope.indexOfModel = function (option) {
					var index = -1;
					if (!$scope.ngModel)
						return;
					angular.forEach(getter($scope.ngModel), function (value, key) {
						if (value == option) {
							index = key;
						}
					});
					return index;
				};
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionsListener);
					watchers.push(watcher);
				} else if ($scope.fsGridConfig.options) {
					if (angular.isString($scope.fsGridConfig.options.data)) {
						watcher = watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options.data), gridOptionDataListener);
						watchers.push(watcher);
					}
				}
				function gridOptionsListener(options) {
					$scope.fsGridConfig.options = options;
					if (!watched && $scope.fsGridConfig.options && angular.isString($scope.fsGridConfig.options.data)) {
						watched = true;
						watchers.push(watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options.data), gridOptionDataListener));
					}
				}
				function gridOptionDataListener(data) {
					$parse('data').assign($scope.fsGridConfig.options, data);
				}

				var activeShowExpression = '', labelExpression = '', requiredExpression = '';
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				contentEL = $templateCache.get('fs/layout/template/fsMultipleSelect.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = contentEL.replace(/REQUIRED_EXPRESSION/g, $scope.fsGridConfig.postPositionRequired);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	// <fs-file-upload ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsFileUpload.$inject = ['$templateCache', '$compile', '$parse', '$timeout', 'FileUploader', 'modalService', 'commonService', 'HttpConstants'];
	function fsFileUpload($templateCache, $compile, $parse, $timeout, FileUploader, modalService, commonService, HttpConstants) {
		var defaultMimeTypes = [
			{
				'label': 'Word',
				'value': 'application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document'
			}, {
				'label': 'Excel',
				'value': 'application/vnd.ms-excel,application/x-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
			}, {
				'label': 'PDF',
				'value': 'application/pdf'
			}, {
				'label': 'Image',
				'value': 'image/*'
			}, {
				'label': 'XML',
				'value': 'text/xml'
			}, {
				'label': 'Text',
				'value': 'text/*'
			}];
		var defaultFileUploadOptions = {
			url: 'file/upload.do',
			downloadUrl: 'file/download.do',
			alias: 'file',
			// headers {Object}: Headers to be sent along with the files. HTML5 browsers only.
			// formData {Array}: Data to be sent along with the files
			autoUpload: true,
			method: "POST",
			removeAfterUpload: true,
			isMultiple: false,
			mimeTypes: "",
			withCredentials: true
		};
		defaultFileUploadOptions.headers = {};
		defaultFileUploadOptions.headers[HttpConstants.DEF_XHRHEADERNAME] = HttpConstants.DEF_XHRHEADERVALUE;
		return {
			restrict: 'EA',
			require: ['^fsGrid', 'fsFileUpload'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var contentEL, content, watcher, watched, watchers = [];
				var log = ctrls[0].getGridAppScope().loggerFactory.getLogger('fsFileUpload');
				initComponent(ctrls[1], ctrls[0]);
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionListener);
					watchers.push(watcher);
				} else if ($scope.fsGridConfig.options && angular.isString($scope.fsGridConfig.options.data)) {
					watchers.push(watchGridScope(ctrls[0], $scope.fsGridConfig.options.data, gridOptionDataListener));
				} else if (!$scope.fsGridConfig.options) {
					$scope.fsGridConfig.options = { 'data': defaultMimeTypes, 'hiddenMimeOptions': false };
				} else if (!$scope.fsGridConfig.options.data) {
					$scope.fsGridConfig.options.data = defaultMimeTypes;
				}
				$scope._remove = function (fileModel) {
					var i, files = $scope.ngModel[$scope.fsGridConfig.model];
					for (i = 0; i < files.length; i++) {
						if (fileModel.docId == files[i].docId) {
							files.splice(i, 1);
							break;
						}
					}
				};
				$scope._download = function ($event, fileModel) {
					var btn = ($event.srcElement || $event.target).parentElement, downloadUrl = $scope._fileUploadOptions.downloadUrl;
					if (btn.disabled) {
						return false;
					}
					btn.disabled = true;
					$timeout(function () {
						btn.disabled = false;
					}, 1000);
					if (fileModel.docId) {
						commonService.download({
							voFile: {
								docId: fileModel.docId
							}
						}, downloadUrl);
					}
				};
				$scope._open = function ($event, fileModel) {
					var i, btn = ($event.srcElement || $event.target).parentElement, downloadUrl = $scope._fileUploadOptions.downloadUrl;
					if (btn.disabled) {
						return false;
					}
					btn.disabled = true;
					if (fileModel.docId) {
						commonService.openDocument({
							voFile: {
								docId: fileModel.docId
							}
						}, downloadUrl).then(function (r) {
							btn.disabled = false;
						}, function (r) {
							btn.disabled = false;
						});
					}
				};
				buildFileUploader();
				function translateScalars(str) {
					if (angular.isString(str)) {
						if (str.search(/kb/i) === str.length - 2) {
							return parseFloat(str.substring(0, str.length - 2) * 1024);
						} else if (str.search(/mb/i) === str.length - 2) {
							return parseFloat(str.substring(0, str.length - 2) * 1048576);
						} else if (str.search(/gb/i) === str.length - 2) {
							return parseFloat(str.substring(0, str.length - 2) * 1073741824);
						} else if (str.search(/b/i) === str.length - 1) {
							return parseFloat(str.substring(0, str.length - 1));
						} else {
							return parseFloat(str);
						}
					}
					return str;
				}
				function buildFileUploader() {
					$scope._fileUploadOptions = angular.merge({}, defaultFileUploadOptions);
					$scope._fileUploadOptions.url = ($scope.fsGridConfig.options && $scope.fsGridConfig.options.url) ? $scope.fsGridConfig.options.url : $scope._fileUploadOptions.url;
					$scope._fileUploadOptions.downloadUrl = ($scope.fsGridConfig.options && $scope.fsGridConfig.options.downloadUrl) ? $scope.fsGridConfig.options.downloadUrl : $scope._fileUploadOptions.downloadUrl;
					$scope._fileUploadOptions.isMultiple = ($scope.fsGridConfig.options && $scope.fsGridConfig.options.isMultiple) ? $scope.fsGridConfig.options.isMultiple : $scope._fileUploadOptions.isMultiple;
					$scope._fileUploadOptions.mimeTypes = ($scope.fsGridConfig.options && $scope.fsGridConfig.options.mimeTypes) ? $scope.fsGridConfig.options.mimeTypes : $scope._fileUploadOptions.mimeTypes;
					$scope._fileUploadOptions.formatRegExps = ($scope.fsGridConfig.options && $scope.fsGridConfig.options.formatRegExps) ? $scope.fsGridConfig.options.formatRegExps : [];
					$scope._fileUploadOptions.formatErrorMsgKey = ($scope.fsGridConfig.options && $scope.fsGridConfig.options.formatErrorMsgKey) ? $scope.fsGridConfig.options.formatErrorMsgKey : '';
					$scope._fileUploadOptions.maxFileSize = translateScalars(($scope.fsGridConfig.options && $scope.fsGridConfig.options.maxFileSize) ? $scope.fsGridConfig.options.maxFileSize : $scope._fileUploadOptions.maxFileSize);
					$scope._fileUploadOptions.minFileSize = translateScalars(($scope.fsGridConfig.options && $scope.fsGridConfig.options.minFileSize) ? $scope.fsGridConfig.options.minFileSize : ($scope._fileUploadOptions.minFileSize || '1b'));
					if ($scope.fsGridConfig.options && !isNaN($scope.fsGridConfig.options.fileCount)) {
						$scope._fileUploadOptions.queueLimit = $scope.fsGridConfig.options.fileCount;
						if ($scope._fileUploadOptions.queueLimit > 1) {
							$scope._fileUploadOptions.isMultiple = true;
						}
					}
					$scope._uploader = new FileUploader($scope._fileUploadOptions);
					$scope._uploader.filters.push({
						'name': 'fileSize',
						'fn': function (fileItem, options) {
							if (angular.isNumber(options.minFileSize) && angular.isNumber(options.maxFileSize)) {
								return fileItem.size >= options.minFileSize && fileItem.size <= options.maxFileSize;
							} else if (angular.isNumber(options.minFileSize)) {
								return fileItem.size >= options.minFileSize;
							} else if (angular.isNumber(options.maxFileSize)) {
								return fileItem.size <= options.maxFileSize;
							}
							return true;
						}
					});
					$scope._uploader.filters.push({
						'name': 'accept',
						'fn': function (fileItem, options) {
							var mineTypeArray = [], fileMineType = "", fileMineTypeWithStar = "", fileName = "";
							if ($scope._fileUploadOptions.mimeTypes) {
								mineTypeArray = $scope._fileUploadOptions.mimeTypes.split(",");
								fileMineType = fileItem.type;
								fileMineTypeWithStar = fileItem.type.substring(0, fileItem.type.indexOf("/") + 1) + "*";
								for (var i = 0; i < mineTypeArray.length; i++) {
									var mineType = mineTypeArray[i].trim();
									if (fileMineType == mineType || fileMineTypeWithStar == mineType) {
										return true;
										break;
									}
								}
								return false;
							} else if ($scope._fileUploadOptions.formatRegExps) {
								fileName = fileItem.name;
								for (var i = 0; i < $scope._fileUploadOptions.formatRegExps.length; i++) {
									var regExpression = new RegExp($scope._fileUploadOptions.formatRegExps[i], "i");
									if (regExpression.test(fileName)) {
										return true;
										break;
									}
								}
								return $scope._fileUploadOptions.formatRegExps.length > 0 ? false : true;
							} else {
								return true;
							}
						}
					});
					$scope._uploader.filters.push({
						'name': 'customerQueueLimit',
						'fn': function (fileItem, options) {
							return !$scope.ngModel[$scope.fsGridConfig.model] || !options.queueLimit || $scope.ngModel[$scope.fsGridConfig.model].length < options.queueLimit;
						}
					});
					$scope._uploader.onAfterAddingFile = function (fileItem) {
						var fileSelect = $element[0].querySelector('#fileSelect');
						var type = getDocumentType(response.data);
						fileItem.icon = type;
						fileSelect.value = null;
					};
					$scope._uploader.onAfterAddingFile = onAfterAddingFile;
					$scope._uploader.onSuccessItem = onSuccessItem;
					$scope._uploader.onErrorItem = onErrorItem;
					$scope._uploader.onWhenAddingFileFailed = onWhenAddingFileFailed;
				}
				function onAfterAddingFile(fileItem) {
					var fileModel = {};
					var type = getDocumentType(fileItem.file.name);
					fileModel.fileItem = fileItem;
					fileModel.type = type;
					fileModel.icon = type;
					fileItem.fileModel = fileModel;
					$scope.ngModel[$scope.fsGridConfig.model] = $scope.ngModel[$scope.fsGridConfig.model] || [];
					$scope.ngModel[$scope.fsGridConfig.model].push(fileModel);
				}
				function onSuccessItem(fileItem, response, status, headers) {
					log.debug('file upload success:', fileItem, response, status, headers);
					if (fileItem.isSuccess && !response.error) {
						angular.merge(fileItem.fileModel, response.data);
						fileItem.docId = response.data.docId;
						fileItem.fileModel.fileItem = {
							progress: 100,
							file: {
								name: fileItem.fileModel.docFileName
							}
						};
					} else if (response.error) {
						var i, fileSelect = $element[0].querySelector('#fileSelect');
						if (fileSelect) {
							fileSelect.value = null;
						}
						for (i = 0; i < $scope.ngModel[$scope.fsGridConfig.model].length; i++) {
							if (fileItem.fileModel == $scope.ngModel[$scope.fsGridConfig.model][i]) {
								$scope.ngModel[$scope.fsGridConfig.model].splice(i, 1);
								break;
							}
						}
						modalService.openErrorDialogs({
							'messages': response.error.errorMessages ? response.error.errorMessages : ['error.fileupload.parse.invalid-file']
						});
					}
					//fileItem.remove();
				}
				function onErrorItem(fileItem, response, status, headers) {
					log.debug('file upload error', fileItem, status, headers);
					var i, fileSelect = $element[0].querySelector('#fileSelect');
					if (fileSelect) {
						fileSelect.value = null;
					}
					for (i = 0; i < $scope.ngModel[$scope.fsGridConfig.model].length; i++) {
						if (fileItem.fileModel == $scope.ngModel[$scope.fsGridConfig.model][i]) {
							$scope.ngModel[$scope.fsGridConfig.model].splice(i, 1);
							break;
						}
					}
				}
				function onWhenAddingFileFailed(fileItem, filter, options) {
					if (filter.name == 'fileSize') {
						if (fileItem.size == 0) {
							modalService.openErrorDialogs({
								'messages': ['label.global.file.empty']
							});
						} else if (fileItem.size > 0 && fileItem.size > options.maxFileSize) {
							modalService.openErrorDialogs({
								'messages': [{ msg: 'label.global.file.maxfilesize', args: [options.maxFileSize] }]

							});
						} else if (fileItem.size < options.minFileSize) {
							modalService.openErrorDialogs({
								'messages': [{ msg: 'label.global.file.minfilesize', args: [options.minFileSize] }]
							});
						}
					} else if (filter.name == 'customerQueueLimit') {
						modalService.openErrorDialogs({
							'messages': [{ msg: 'label.global.file.maxfilesize', args: [options.queueLimit] }]
						});
					} else if (filter.name == 'accept') {
						var formatErrorMsgKey = $scope._fileUploadOptions.formatErrorMsgKey || 'global.error.file.format';
						modalService.openErrorDialogs({
							'messages': [formatErrorMsgKey]
						});
					}
					log.debug('file upload error', fileItem, filter, options);
					$element[0].querySelector('#fileSelect').value = null;
				}
				function getDocumentType(fileName) {
					if (/.*\.((jpg)|(jpeg))$/i.test(fileName)) {
						return 'jpg';
					} else if (/.*\.((doc)|(docx))$/i.test(fileName)) {
						return 'doc';
					} else if (/.*\.((xls)|(xlsx))$/i.test(fileName)) {
						return 'xls';
					}
					var match = fileName.match(/.*\.([a-zA-Z]+)/);
					return match && match.length == 2 ? match[1].toLowerCase() : 'file';
				}
				function gridOptionListener(options) {
					$scope.fsGridConfig.options = options;
					if (!watched && $scope.fsGridConfig.options && angular.isString($scope.fsGridConfig.options.data)) {
						watched = true;
						watchers.push(watchGridScope(ctrls[0], $scope.fsGridConfig.options.data, gridOptionDataListener));
					} else if (!$scope.fsGridConfig.options.data) {
						$scope.fsGridConfig.options.data = defaultMimeTypes;
					}
					if ($scope.fsGridConfig.options) {
						buildFileUploader();
					}
				}
				function gridOptionDataListener(data) {
					$parse('data').assign($scope.fsGridConfig.options, data);
				}

				var activeShowExpression = '', labelExpression = '', requiredExpression = '';
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				contentEL = $templateCache.get('fs/layout/template/fsFileUpload.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = contentEL.replace(/REQUIRED_EXPRESSION/g, $scope.fsGridConfig.postPositionRequired);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	// <fs-typeahead ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsTypeahead.$inject = ['$templateCache', '$compile', '$parse', '$translate'];
	function fsTypeahead($templateCache, $compile, $parse, $translate) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', '^^?form', 'fsTypeahead'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var contentEL, content, watcher, watched, watchers = [];
				initComponent(ctrls[2], ctrls[0]);
				$scope._typeaheadModel = {};
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionListener);
					watchers.push(watcher);
				} else if ($scope.fsGridConfig.options) {
					if (angular.isString($scope.fsGridConfig.options.data)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.data, gridOptionDataListener);
						watchers.push(watcher);
					}
				}
				$scope.changeGridModelValue = function () {
					setGridModel($parse('_typeaheadModel.label')($scope));
				};
				$scope.typeaheadOnSelect = function ($item, $model, $label, $event) {
					$parse($scope.fsGridConfig.model).assign($scope.ngModel, $item.value);
				}
				watcher = $scope.$watch('ngModel.' + $scope.fsGridConfig.model, function (newValue) {
					setTypeaheadDisplay();
				});
				watchers.push(watcher);
				function gridOptionListener(options) {
					$scope.fsGridConfig.options = options || {};
					if (!watched && angular.isString($scope.fsGridConfig.options.data)) {
						watched = true;
						watchers.push(watchGridScope(ctrls[0], $scope.fsGridConfig.options.data, gridOptionDataListener));
					}
				}
				function gridOptionDataListener(data) {
					$parse('data').assign($scope.fsGridConfig.options, data);
					setTypeaheadDisplay();
				}
				function setGridModel(labelValue) {
					var i;
					if (!$scope.fsGridConfig.options || !$scope.fsGridConfig.options.data || !labelValue) {
						$parse($scope.fsGridConfig.model).assign($scope.ngModel, null);
						return;
					}
					for (i = 0; i < $scope.fsGridConfig.options.data.length; i++) {
						if ($scope.fsGridConfig.options.data[i].label == labelValue) {
							$parse($scope.fsGridConfig.model).assign($scope.ngModel, $scope.fsGridConfig.options.data[i].value);
							break;
						}
					}
					if (i >= $scope.fsGridConfig.options.data.length) {
						$parse($scope.fsGridConfig.model).assign($scope.ngModel, labelValue);
					}
				}
				function setTypeaheadDisplay() {
					var i, modelValue = $parse($scope.fsGridConfig.model)($scope.ngModel);
					if (!$scope.fsGridConfig.options || !$scope.fsGridConfig.options.data || !$scope.ngModel) {
						return;
					}
					for (i = 0; i < $scope.fsGridConfig.options.data.length; i++) {
						if ($scope.fsGridConfig.options.data[i].value == modelValue) {
							$translate($scope.fsGridConfig.options.data[i].label).then(function (display) {
								$parse('_typeaheadModel.label').assign($scope, display);
							}, angular.noop);
							break;
						}
					}
					if (i >= $scope.fsGridConfig.options.data.length) {
						$parse('_typeaheadModel.label').assign($scope, modelValue);
					}
				}

				var activeShowExpression = '', labelExpression = '', requiredExpression = '';
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				contentEL = $templateCache.get('fs/layout/template/fsTypeahead.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = contentEL.replace(/REQUIRED_EXPRESSION/g, $scope.fsGridConfig.postPositionRequired);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	// <fs-sensitive ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsSensitive.$inject = ['$templateCache', '$compile', '$parse', 'formatService'];
	function fsSensitive($templateCache, $compile, $parse, formatService) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', '^^?form', 'fsSensitive'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var contentEL, content, watcher, watchers = [];
				initComponent(ctrls[2], ctrls[0]);
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				$scope.getDisplay = function () {
					return formatService.formatSst($parse('ngModel.' + $scope.fsGridConfig.model)($scope), $scope.fsGridConfig.options);
				}
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionListener);
					watchers.push(watcher);
				}
				function gridOptionListener(options) {
					$scope.fsGridConfig.options = options;
				}

				var activeShowExpression = '', labelExpression = '', requiredExpression = '';
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				contentEL = $templateCache.get('fs/layout/template/fsSensitive.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = contentEL.replace(/REQUIRED_EXPRESSION/g, $scope.fsGridConfig.postPositionRequired);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	//<fs-money ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsMoney.$inject = ['$templateCache', '$compile', 'storageService', '$parse', 'modalService'];
	function fsMoney($templateCache, $compile, storageService, $parse, modalService) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', '^^?form', 'fsMoney'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var contentEL, content, formatInfo = storageService.getFormatInfo(), watcher, watchers = [];
				initComponent(ctrls[2], ctrls[0]);
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				$scope.handlePaste = function ($event) {
					var i, charCode, text = $event.clipboardData ? $event.clipboardData.getData('text/plain') : null;
					if (text) {
						if ($scope.fsGridConfig && $scope.fsGridConfig.options && $scope.fsGridConfig.options.allowNegative && text.indexOf('-') == 0) {
							text = text.substring(1, text.length);
						}
						for (i = 0; i < text.length; i++) {
							charCode = text.charCodeAt(i);
							if (text.indexOf('.') >= 0 && text.indexOf('.') != i && charCode == 46) {
								modalService.openErrorDialogs({
									messages: ['error.clipboard.invalid']
								});
								$event.returnValue = false;
								$event.preventDefault();
								$event.stopPropagation();
								return false;
							}
							if ((charCode > 57 || charCode < 48) && charCode != 46) {
								modalService.openErrorDialogs({
									messages: ['error.clipboard.invalid']
								});
								$event.returnValue = false;
								$event.preventDefault();
								$event.stopPropagation();
								return false;
							}
						}
					}
				};
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options), gridOptionListener);
					watchers.push(watcher);
				}
				if ($scope.fsGridConfig.format && /^[#0.,]+$/.test($scope.fsGridConfig.format)) {
					$scope.fsGridConfig.placeholder = $scope.fsGridConfig.format;
				} else {
					watcher = $scope.$watch('ngModel.' + $scope.fsGridConfig.format, function (formatter) {
						var filter;
						if (formatter == $scope.fsGridConfig.format) {
							return;
						}
						try {
							if (formatter && /^[#0.,]+$/.test(formatter)) {
								$scope.fsGridConfig.placeholder = $scope.fsGridConfig.format;
							} else {
								$scope.fsGridConfig.placeholder = formatter = formatInfo[formatter || $scope.fsGridConfig.format] || formatInfo['defaultMoney'];
							}
						} catch (e) {
							$scope.fsGridConfig.placeholder = formatter = $scope.fsGridConfig.format || formatInfo['defaultMoney'];
						}
						if (formatter && ctrls[2].showStyle($scope.fsGridConfig) == 'view') {
							filter = $scope.fsGridConfig.filter ? $scope.fsGridConfig.filter : 'moneyFormat';
							$scope.moneyDisplay = $parse('ngModel.' + $scope.fsGridConfig.model + ' | ' + filter + ':\'' + formatter + '\'')($scope);
						}
					});
					watchers.push(watcher);
				}
				watcher = $scope.$watch('ngModel.' + $scope.fsGridConfig.model, function (model) {
					var formatter, filter;
					if ($scope.fsGridConfig.format && ctrls[2].showStyle($scope.fsGridConfig) == 'view') {
						if (formatInfo[$scope.fsGridConfig.format]) {
							formatter = formatInfo[$scope.fsGridConfig.format];
						} else {
							formatter = $parse('ngModel.' + $scope.fsGridConfig.format)($scope) || formatInfo['defaultMoney'];
						}
						filter = $scope.fsGridConfig.filter ? $scope.fsGridConfig.filter : 'moneyFormat';
						$scope.moneyDisplay = $parse('ngModel.' + $scope.fsGridConfig.model + ' | ' + filter + ':\'' + formatter + '\'')($scope);
					}
				}, true);
				watchers.push(watcher);
				function gridOptionListener(options) {
					$scope.fsGridConfig.options = options;
				}

				var activeShowExpression = '', labelExpression = '', requiredExpression = '';
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				contentEL = $templateCache.get('fs/layout/template/fsMoney.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = contentEL.replace(/REQUIRED_EXPRESSION/g, $scope.fsGridConfig.postPositionRequired);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	// <fs-money-range ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsMoneyRange.$inject = ['$templateCache', '$compile', 'storageService', '$parse', 'modalService'];
	function fsMoneyRange($templateCache, $compile, storageService, $parse, modalService) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', '^^?form', 'fsMoneyRange'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var contentEL, content, formatInfo = storageService.getFormatInfo(), watcher, watchers = [];
				initComponent(ctrls[2], ctrls[0]);
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				$scope.handlePaste = function ($event) {
					var i, charCode, text;
					if (window.clipboardData && window.clipboardData.getData) {
						text = window.clipboardData ? window.clipboardData.getData('Text') : null;
					} else {
						text = $event.clipboardData ? $event.clipboardData.getData('text/plain') : null;
					}
					if (text) {
						if ($scope.fsGridConfig && $scope.fsGridConfig.options && $scope.fsGridConfig.options.allowNegative && text.indexOf('-') == 0) {
							text = text.substring(1, text.length);
						}
						for (i = 0; i < text.length; i++) {
							charCode = text.charCodeAt(i);
							if (text.indexOf('.') >= 0 && text.indexOf('.') != i && charCode == 46) {
								modalService.openErrorDialogs({
									messages: ['error.clipboard.invalid']
								});
								$event.returnValue = false;
								$event.preventDefault();
								$event.stopPropagation();
								return false;
							}
							if ((charCode > 57 || charCode < 48) && charCode != 46) {
								modalService.openErrorDialogs({
									messages: ['error.clipboard.invalid']
								});
								$event.returnValue = false;
								$event.preventDefault();
								$event.stopPropagation();
								return false;
							}
						}
					}
				};
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionListener);
					watchers.push(watcher);
				}
				if ($scope.fsGridConfig.format && /^[#0.,]+$/.test($scope.fsGridConfig.format)) {
					$scope.fsGridConfig.placeholder = $scope.fsGridConfig.format;
				} else {
					watcher = $scope.$watch($attrs.ngModel + '.' + $scope.fsGridConfig.format, function (formatter) {
						var filter;
						if (formatter == $scope.fsGridConfig.format) {
							return;
						}
						try {
							if (formatter && /^[#0.,]+$/.test(formatter)) {
								$scope.fsGridConfig.placeholder = $scope.fsGridConfig.format;
							} else {
								$scope.fsGridConfig.placeholder = formatter = formatInfo[formatter || $scope.fsGridConfig.format] || formatInfo['defaultMoney'];
							}
						} catch (e) {
							$scope.fsGridConfig.placeholder = formatter = $scope.fsGridConfig.format || formatInfo['defaultMoney'];
						}
						if (formatter && ctrls[2].showStyle($scope.fsGridConfig) == 'view') {
							filter = $scope.fsGridConfig.filter ? $scope.fsGridConfig.filter : 'moneyFormat';
							$scope.fromMoneyDisplay = $parse('ngModel.' + $scope.fsGridConfig.model + '.from | ' + filter + ':\'' + formatter + '\'')($scope);
							$scope.toMoneyDisplay = $parse('ngModel.' + $scope.fsGridConfig.model + '.to | ' + filter + ':\'' + formatter + '\'')($scope);
						}
					});
					watchers.push(watcher);
				}
				watcher = $scope.$watch('ngModel.' + $scope.fsGridConfig.model, function (model) {
					var formatter, filter;
					if ($scope.fsGridConfig.format && ctrls[2].showStyle($scope.fsGridConfig) == 'view') {
						if (formatInfo[$scope.fsGridConfig.format]) {
							formatter = formatInfo[$scope.fsGridConfig.format];
						} else {
							formatter = $parse('ngModel.' + $scope.fsGridConfig.format)($scope) || formatInfo['defaultMoney'];
						}
						filter = $scope.fsGridConfig.filter ? $scope.fsGridConfig.filter : 'moneyFormat';
						$scope.fromMoneyDisplay = $parse('ngModel.' + $scope.fsGridConfig.model + '.from | ' + filter + ':\'' + formatter + '\'')($scope);
						$scope.toMoneyDisplay = $parse('ngModel.' + $scope.fsGridConfig.model + '.to | ' + filter + ':\'' + formatter + '\'')($scope);
					}
				}, true);
				watchers.push(watcher);
				function gridOptionListener(options) {
					$scope.fsGridConfig.options = options;
				}

				var activeShowExpression = '', labelExpression = '', requiredExpression = '';
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				contentEL = $templateCache.get('fs/layout/template/fsMoneyRange.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = contentEL.replace(/REQUIRED_EXPRESSION/g, $scope.fsGridConfig.postPositionRequired);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	//<fs-number ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsNumber.$inject = ['$templateCache', '$compile', 'storageService', '$parse', 'modalService'];
	function fsNumber($templateCache, $compile, storageService, $parse, modalService) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', '^^?form', 'fsNumber'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var contentEL, content, formatInfo = storageService.getFormatInfo(), watcher, watchers = [];
				$scope.fsGridConfig.placeholder = formatInfo[$scope.fsGridConfig.format] || $scope.fsGridConfig.format;
				initComponent(ctrls[2], ctrls[0]);
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				$scope.handlePaste = function ($event) {
					var i, charCode, text = $event.clipboardData ? $event.clipboardData.getData('text/plain') : null;
					if (text) {
						if ($scope.fsGridConfig && $scope.fsGridConfig.options && $scope.fsGridConfig.options.allowNegative && text.indexOf('-') == 0) {
							text = text.substring(1, text.length);
						}
						for (i = 0; i < text.length; i++) {
							charCode = text.charCodeAt(i);
							if (text.indexOf('.') >= 0 && text.indexOf('.') != i && charCode == 46) {
								modalService.openErrorDialogs({
									messages: ['error.clipboard.invalid']
								});
								$event.returnValue = false;
								$event.preventDefault();
								$event.stopPropagation();
								return false;
							}
							if ((charCode > 57 || charCode < 48) && charCode != 46) {
								modalService.openErrorDialogs({
									messages: ['error.clipboard.invalid']
								});
								$event.returnValue = false;
								$event.preventDefault();
								$event.stopPropagation();
								return false;
							}
						}
					}
				};
				if ($scope.fsGridConfig.format && /^[#0.,]+$/.test($scope.fsGridConfig.format)) {
					$scope.fsGridConfig.placeholder = $scope.fsGridConfig.format;
				} else {
					watcher = $scope.$watch('ngModel.' + $scope.fsGridConfig.format, function (formatter) {
						var filter;
						if (formatter == $scope.fsGridConfig.format) {
							return;
						}
						try {
							if (formatter && /^[#0.,]+$/.test(formatter)) {
								$scope.fsGridConfig.placeholder = $scope.fsGridConfig.format;
							} else {
								$scope.fsGridConfig.placeholder = formatter = formatInfo[formatter || $scope.fsGridConfig.format] || formatInfo['defaultFloat'];
							}
						} catch (e) {
							$scope.fsGridConfig.placeholder = formatter = $scope.fsGridConfig.format || formatInfo['defaultFloat'];
						}
						if (formatter && ctrls[2].showStyle($scope.fsGridConfig) == 'view') {
							filter = $scope.fsGridConfig.filter ? $scope.fsGridConfig.filter : 'numberFormat';
							$scope.numberDisplay = $parse('ngModel.' + $scope.fsGridConfig.model + ' | ' + filter + ':\'' + formatter + '\'')($scope);
						}
					});
					watchers.push(watcher);
				}
				watcher = $scope.$watch('ngModel.' + $scope.fsGridConfig.model, function (model) {
					var formatter, filter;
					if ($scope.fsGridConfig.format && ctrls[2].showStyle($scope.fsGridConfig) == 'view') {
						if (formatInfo[$scope.fsGridConfig.format]) {
							formatter = formatInfo[$scope.fsGridConfig.format];
						} else {
							formatter = $parse('ngModel.' + $scope.fsGridConfig.format)($scope) || formatInfo['defaultFloat'];
						}
						filter = $scope.fsGridConfig.filter ? $scope.fsGridConfig.filter : 'numberFormat';
						$scope.numberDisplay = $parse('ngModel.' + $scope.fsGridConfig.model + ' | ' + filter + ':\'' + formatter + '\'')($scope);
					}
				}, true);
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options), gridOptionListener);
					watchers.push(watcher);
				}
				function gridOptionListener(options) {
					$scope.fsGridConfig.options = options;
				}

				var activeShowExpression = '', labelExpression = '', requiredExpression = '';
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				contentEL = $templateCache.get('fs/layout/template/fsNumber.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = contentEL.replace(/REQUIRED_EXPRESSION/g, $scope.fsGridConfig.postPositionRequired);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	// <fs-number-range ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsNumberRange.$inject = ['$templateCache', '$compile', 'storageService', '$parse', 'modalService'];
	function fsNumberRange($templateCache, $compile, storageService, $parse, modalService) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', '^^?form', 'fsNumberRange'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var contentEL, content, formatInfo = storageService.getFormatInfo(), watcher, watchers = [];
				$scope.fsGridConfig.placeholder = formatInfo[$scope.fsGridConfig.format] || $scope.fsGridConfig.format;
				initComponent(ctrls[2], ctrls[0]);
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				$scope.handlePaste = function ($event) {
					var i, charCode, text;
					if (window.clipboardData && window.clipboardData.getData) {
						text = window.clipboardData ? window.clipboardData.getData('Text') : null;
					} else {
						text = $event.clipboardData ? $event.clipboardData.getData('text/plain') : null;
					}
					if (text) {
						if ($scope.fsGridConfig && $scope.fsGridConfig.options && $scope.fsGridConfig.options.allowNegative && text.indexOf('-') == 0) {
							text = text.substring(1, text.length);
						}
						for (i = 0; i < text.length; i++) {
							charCode = text.charCodeAt(i);
							if (text.indexOf('.') >= 0 && text.indexOf('.') != i && charCode == 46) {
								modalService.openErrorDialogs({
									messages: ['error.clipboard.invalid']
								});
								$event.returnValue = false;
								$event.preventDefault();
								$event.stopPropagation();
								return false;
							}
							if ((charCode > 57 || charCode < 48) && charCode != 46) {
								modalService.openErrorDialogs({
									messages: ['error.clipboard.invalid']
								});
								$event.returnValue = false;
								$event.preventDefault();
								$event.stopPropagation();
								return false;
							}
						}
					}
				};
				if ($scope.fsGridConfig.format && /^[#0.,]+$/.test($scope.fsGridConfig.format)) {
					$scope.fsGridConfig.placeholder = $scope.fsGridConfig.format;
				} else {
					watcher = $scope.$watch('ngModel.' + $scope.fsGridConfig.format, function (formatter) {
						var filter;
						if (formatter == $scope.fsGridConfig.format) {
							return;
						}
						try {
							if (formatter && /^[#0.,]+$/.test(formatter)) {
								$scope.fsGridConfig.placeholder = $scope.fsGridConfig.format;
							} else {
								$scope.fsGridConfig.placeholder = formatter = formatInfo[formatter || $scope.fsGridConfig.format] || formatInfo['defaultFloat'];
							}
						} catch (e) {
							$scope.fsGridConfig.placeholder = formatter = $scope.fsGridConfig.format || formatInfo['defaultFloat'];
						}
						if (formatter && ctrls[2].showStyle($scope.fsGridConfig) == 'view') {
							filter = $scope.fsGridConfig.filter ? $scope.fsGridConfig.filter : 'numberFormat';
							$scope.fromNumberDisplay = $parse('ngModel.' + $scope.fsGridConfig.model + '.from | ' + filter + ':\'' + formatter + '\'')($scope);
							$scope.toNumberDisplay = $parse('ngModel.' + $scope.fsGridConfig.model + '.to | ' + filter + ':\'' + formatter + '\'')($scope);
						}
					});
					watchers.push(watcher);
				}
				watcher = $scope.$watch('ngModel.' + $scope.fsGridConfig.model, function (model) {
					var formatter, filter;
					if ($scope.fsGridConfig.format && ctrls[2].showStyle($scope.fsGridConfig) == 'view') {
						if (formatInfo[$scope.fsGridConfig.format]) {
							formatter = formatInfo[$scope.fsGridConfig.format];
						} else {
							formatter = $parse('ngModel.' + $scope.fsGridConfig.format)($scope) || formatInfo['defaultFloat'];
						}
						filter = $scope.fsGridConfig.filter ? $scope.fsGridConfig.filter : 'numberFormat';
						$scope.fromNumberDisplay = $parse('ngModel.' + $scope.fsGridConfig.model + '.from | ' + filter + ':\'' + formatter + '\'')($scope);
						$scope.toNumberDisplay = $parse('ngModel.' + $scope.fsGridConfig.model + '.to | ' + filter + ':\'' + formatter + '\'')($scope);
					}
				}, true);
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionListener);
					watchers.push(watcher);
				}
				function gridOptionListener(options) {
					$scope.fsGridConfig.options = options;
				}

				var activeShowExpression = '', labelExpression = '', requiredExpression = '';
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				contentEL = $templateCache.get('fs/layout/template/fsNumberRange.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = contentEL.replace(/REQUIRED_EXPRESSION/g, $scope.fsGridConfig.postPositionRequired);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	//<fs-percentage ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsPercentage.$inject = ['$templateCache', '$compile', '$parse', 'storageService', 'modalService'];
	function fsPercentage($templateCache, $compile, $parse, storageService, modalService) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', '^^?form', 'fsPercentage'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var contentEL, content, formatInfo = storageService.getFormatInfo(), watcher, watchers = [];
				$scope.fsGridConfig.placeholder = formatInfo[$scope.fsGridConfig.format] || $scope.fsGridConfig.format;
				initComponent(ctrls[2], ctrls[0]);
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				$scope.handlePaste = function ($event) {
					var i, charCode, text = $event.clipboardData ? $event.clipboardData.getData('text/plain') : null;
					if (text) {
						if ($scope.fsGridConfig && $scope.fsGridConfig.options && $scope.fsGridConfig.options.allowNegative && text.indexOf('-') == 0) {
							text = text.substring(1, text.length);
						}
						for (i = 0; i < text.length; i++) {
							charCode = text.charCodeAt(i);
							if (text.indexOf('.') >= 0 && text.indexOf('.') != i && charCode == 46) {
								modalService.openErrorDialogs({
									messages: ['error.clipboard.invalid']
								});
								$event.returnValue = false;
								$event.preventDefault();
								$event.stopPropagation();
								return false;
							}
							if ((charCode > 57 || charCode < 48) && charCode != 46) {
								modalService.openErrorDialogs({
									messages: ['error.clipboard.invalid']
								});
								$event.returnValue = false;
								$event.preventDefault();
								$event.stopPropagation();
								return false;
							}
						}
					}
				};
				if ($scope.fsGridConfig.format && /^[#0.,]+$/.test($scope.fsGridConfig.format)) {
					$scope.fsGridConfig.placeholder = $scope.fsGridConfig.format;
				} else {
					watcher = $scope.$watch('ngModel.' + $scope.fsGridConfig.format, function (formatter) {
						var filter;
						if (formatter == $scope.fsGridConfig.format) {
							return;
						}
						try {
							if (formatter && /^[#0.,]+$/.test(formatter)) {
								$scope.fsGridConfig.placeholder = $scope.fsGridConfig.format;
							} else {
								$scope.fsGridConfig.placeholder = formatter = formatInfo[formatter || $scope.fsGridConfig.format] || formatInfo['defaultFloat'];
							}
						} catch (e) {
							$scope.fsGridConfig.placeholder = formatter = $scope.fsGridConfig.format || formatInfo['defaultFloat'];
						}
						if (formatter && ctrls[2].showStyle($scope.fsGridConfig) == 'view') {
							filter = $scope.fsGridConfig.filter ? $scope.fsGridConfig.filter : 'numberFormat';
							$scope.percentageDisplay = $parse('ngModel.' + $scope.fsGridConfig.model + ' | ' + filter + ':\'' + formatter + '\'')($scope);
						}
					});
					watchers.push(watcher);
				}
				watcher = $scope.$watch('ngModel.' + $scope.fsGridConfig.model, function (model) {
					var formatter, filter;
					if ($scope.fsGridConfig.format && ctrls[2].showStyle($scope.fsGridConfig) == 'view') {
						if (formatInfo[$scope.fsGridConfig.format]) {
							formatter = formatInfo[$scope.fsGridConfig.format];
						} else {
							formatter = $parse('ngModel.' + $scope.fsGridConfig.format)($scope) || formatInfo['defaultFloat'];
						}
						filter = $scope.fsGridConfig.filter ? $scope.fsGridConfig.filter : 'numberFormat';
						$scope.percentageDisplay = $parse('ngModel.' + $scope.fsGridConfig.model + ' | ' + filter + ':\'' + formatter + '\'')($scope);
					}
				}, true);
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options), gridOptionListener);
					watchers.push(watcher);
				}
				function gridOptionListener(options) {
					$scope.fsGridConfig.options = options;
				}

				var activeShowExpression = '', labelExpression = '', requiredExpression = '';
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				contentEL = $templateCache.get('fs/layout/template/fsPercentage.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = contentEL.replace(/REQUIRED_EXPRESSION/g, $scope.fsGridConfig.postPositionRequired);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	// <fs-percentage-range ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsPercentageRange.$inject = ['$templateCache', '$compile', '$parse', 'storageService', 'modalService'];
	function fsPercentageRange($templateCache, $compile, $parse, storageService, modalService) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', '^^?form', 'fsPercentageRange'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var contentEL, content, formatInfo = storageService.getFormatInfo(), watcher, watchers = [];
				$scope.fsGridConfig.placeholder = formatInfo[$scope.fsGridConfig.format] || $scope.fsGridConfig.format;
				initComponent(ctrls[2], ctrls[0]);
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				$scope.handlePaste = function ($event) {
					var i, charCode, text;
					if (window.clipboardData && window.clipboardData.getData) {
						text = window.clipboardData ? window.clipboardData.getData('Text') : null;
					} else {
						text = $event.clipboardData ? $event.clipboardData.getData('text/plain') : null;
					}
					if (text) {
						if ($scope.fsGridConfig && $scope.fsGridConfig.options && $scope.fsGridConfig.options.allowNegative && text.indexOf('-') == 0) {
							text = text.substring(1, text.length);
						}
						for (i = 0; i < text.length; i++) {
							charCode = text.charCodeAt(i);
							if (text.indexOf('.') >= 0 && text.indexOf('.') != i && charCode == 46) {
								modalService.openErrorDialogs({
									messages: ['error.clipboard.invalid']
								});
								$event.returnValue = false;
								$event.preventDefault();
								$event.stopPropagation();
								return false;
							}
							if ((charCode > 57 || charCode < 48) && charCode != 46) {
								modalService.openErrorDialogs({
									messages: ['error.clipboard.invalid']
								});
								$event.returnValue = false;
								$event.preventDefault();
								$event.stopPropagation();
								return false;
							}
						}
					}
				};
				if ($scope.fsGridConfig.format && /^[#0.,]+$/.test($scope.fsGridConfig.format)) {
					$scope.fsGridConfig.placeholder = $scope.fsGridConfig.format;
				} else {
					watcher = $scope.$watch('ngModel.' + $scope.fsGridConfig.format, function (formatter) {
						var filter;
						if (formatter == $scope.fsGridConfig.format) {
							return;
						}
						try {
							if (formatter && /^[#0.,]+$/.test(formatter)) {
								$scope.fsGridConfig.placeholder = $scope.fsGridConfig.format;
							} else {
								$scope.fsGridConfig.placeholder = formatter = formatInfo[formatter || $scope.fsGridConfig.format] || formatInfo['percentage'];
							}
						} catch (e) {
							$scope.fsGridConfig.placeholder = formatter = $scope.fsGridConfig.format || formatInfo['percentage'];
						}
						if (formatter && ctrls[2].showStyle($scope.fsGridConfig) == 'view') {
							filter = $scope.fsGridConfig.filter ? $scope.fsGridConfig.filter : 'numberFormat';
							$scope.fromPercentageDisplay = $parse('ngModel.' + $scope.fsGridConfig.model + '.from | ' + filter + ':\'' + formatter + '\'')($scope);
							$scope.toPercentageDisplay = $parse('ngModel.' + $scope.fsGridConfig.model + '.to | ' + filter + ':\'' + formatter + '\'')($scope);
						}
					});
					watchers.push(watcher);
				}
				watcher = $scope.$watch('ngModel.' + $scope.fsGridConfig.model, function (model) {
					var formatter, filter;
					if ($scope.fsGridConfig.format && ctrls[2].showStyle($scope.fsGridConfig) == 'view') {
						if (formatInfo[$scope.fsGridConfig.format]) {
							formatter = formatInfo[$scope.fsGridConfig.format];
						} else {
							formatter = $parse('ngModel.' + $scope.fsGridConfig.format)($scope) || formatInfo['defaultFloat'];
						}
						filter = $scope.fsGridConfig.filter ? $scope.fsGridConfig.filter : 'numberFormat';
						$scope.fromPercentageDisplay = $parse('ngModel.' + $scope.fsGridConfig.model + '.from | ' + filter + ':\'' + formatter + '\'')($scope);
						$scope.toPercentageDisplay = $parse('ngModel.' + $scope.fsGridConfig.model + '.to | ' + filter + ':\'' + formatter + '\'')($scope);
					}
				}, true);
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionListener);
					watchers.push(watcher);
				}
				function gridOptionListener(options) {
					$scope.fsGridConfig.options = options;
				}

				var activeShowExpression = '', labelExpression = '', requiredExpression = '';
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				contentEL = $templateCache.get('fs/layout/template/fsPercentageRange.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = contentEL.replace(/REQUIRED_EXPRESSION/g, $scope.fsGridConfig.postPositionRequired);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	// <fs-lookup-text ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsLookupText.$inject = ['$templateCache', '$compile', '$log', '$parse', 'modalService'];
	function fsLookupText($templateCache, $compile, $log, $parse, modalService) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', 'fsLookupText', '^^?uiGrid'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var contentEL, content, watcher, watchers = [], log = $log.getInstance('fsLookupText');;
				initComponent(ctrls[1], ctrls[0]);
				$scope._contextMenuOptions = [
					['Remove',
						function () {
							$parse($scope.fsGridConfig.model).assign($scope.ngModel, null);
						},
						function () {
							return $parse($scope.fsGridConfig.model)($scope.ngModel);
						}
					]
				];
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionListener);
					watchers.push(watcher);
				} else {
					buildOpenCustomDialogsFunction();
				}
				watcher = $scope.$watch($attrs.ngModel, function (ngModel) {
					var boundTo, options;
					if (angular.isString($scope.fsGridConfig.options)) {
						options = $scope.fsGridConfig.appScope.$eval($scope.fsGridConfig.options);
						boundTo = options ? options.boundTo : null;
					} else if (angular.isObject($scope.fsGridConfig.options)) {
						boundTo = $scope.fsGridConfig.options.boundTo;
					}
					if (boundTo) {
						if (ngModel == null || ngModel == undefined || ngModel == '') {
							if (angular.isString(boundTo)) {
								$parse(boundTo).assign($scope.ngModel, null);
							} else {
								angular.forEach(boundTo, function (value, key) {
									if (value != $scope.fsGridConfig.model) {
										$parse(value).assign($scope.ngModel, null);
									}
								});
							}
						}
					}
				});
				watchers.push(watcher);
				function gridOptionListener(options) {
					if (options) {
						$scope.fsGridConfig.options = options;
						buildOpenCustomDialogsFunction();
					}
				}
				function buildOpenCustomDialogsFunction() {
					if ($scope.fsGridConfig.options) {
						// init manualInput value
						$scope.fsGridConfig.options.manualInput = ($scope.fsGridConfig.options.manualInput == undefined) ? true : $scope.fsGridConfig.options.manualInput;
						$scope.openCustomDialogs = function () {
							var params = {};
							angular.forEach($scope.fsGridConfig.options.params, function (parameterExpression, parameterKey) {
								params[parameterKey] = extractParamater(parameterExpression)
							});
							if (!params.currentEntity) params.currentEntity = $scope.ngModel;
							modalService.openCustomDialogs({
								dialogsModule: $scope.fsGridConfig.options.id,
								size: $scope.fsGridConfig.options.size,
								params: params,
								btnCallback: {
									'ok': okCallback,
									'close': closeCallback
								}
							});
						};
					}
				}
				function extractParamater(parameterExpression) {
					var value, appScope = ($scope.uiGrid ? $scope.uiGrid.appScope : ctrls[0].getGridAppScope());
					if (!angular.isString(parameterExpression)) {
						value = parameterExpression;
					} else if (parameterExpression == '<this') {
						if ($scope.ngModel) {
							value = $scope.ngModel;
						} else {
							value = appScope;
						}
					} else if (parameterExpression && parameterExpression.charAt(0) == '<') {
						parameterExpression = parameterExpression.substr(1);
						value = $parse(parameterExpression)($scope.ngModel);
						if (value == undefined) {
							value = $parse(parameterExpression)(appScope);
						}
					} else {
						value = parameterExpression;
					}
					return value;
				}
				function okCallback(lookupResult) {
					var appScope = (ctrls[0].getGridAppScope() || ctrls[2].grid.appScope), callback, result = {}, boundTo = $scope.fsGridConfig.options.boundTo;
					if (!lookupResult || lookupResult.length == 0) {
						modalService.openErrorDialogs({
							messages: ['title.dialogs.info.norecsel']
						});
						return false;
					}
					if (boundTo && angular.isArray(lookupResult) && lookupResult.length == 1) {
						result = lookupResult[0];
						if (angular.isString(boundTo)) {
							$parse(boundTo).assign($scope.ngModel, result);
						} else {
							angular.forEach(boundTo, function (value, key) {
								$parse(value).assign($scope.ngModel, $parse(key)(result));
							});
						}
					}
					if ($scope.fsGridConfig.options.callback) {
						if (angular.isString($scope.fsGridConfig.options.callback.ok)) {
							callback = appScope.$eval($scope.fsGridConfig.options.callback.ok);
							if (!callback) {
								log.debug("cannot found the callback function:" + $scope.fsGridConfig.options.callback.ok);
							} else {
								callback(lookupResult, $scope.ngModel);
							}
						} else if (angular.isFunction($scope.fsGridConfig.options.callback.ok)) {
							$scope.fsGridConfig.options.callback.ok(lookupResult, $scope.ngModel);
						}
					}
					if ($scope.fsGridConfig.options.okEvent) {
						var resultEntity = {
							returnEntities: lookupResult,
							currentEntity: $scope.ngModel
						}
						appScope.$emit(($scope.fsGridConfig.model + '_OK').toUpperCase(), resultEntity);
					}
				}
				function closeCallback(lookupResult) {
					var appScope = (ctrls[0].getGridAppScope() || ctrls[2].grid.appScope);
					if ($scope.fsGridConfig.options.callback) {
						if (angular.isString($scope.fsGridConfig.options.callback.close)) {
							callback = appScope.$eval($scope.fsGridConfig.options.callback.close);
							if (!callback) {
								log.debug("cannot found the callback function:" + $scope.fsGridConfig.options.callback.close);
							} else {
								callback(lookupResult, $scope.ngModel);
							}
						} else if (angular.isFunction($scope.fsGridConfig.options.callback.close)) {
							$scope.fsGridConfig.options.callback.close(lookupResult, $scope.ngModel);
						}
					}
					if ($scope.fsGridConfig.options.cancelEvent) {
						var resultEntity = {
							returnEntities: lookupResult,
							currentEntity: $scope.ngModel
						};
						appScope.$emit(($scope.fsGridConfig.model + '_CANCEL').toUpperCase(), resultEntity);
					}
				}

				var activeShowExpression = '', labelExpression = '', requiredExpression = '';
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				contentEL = $templateCache.get('fs/layout/template/fsLookupText.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				if ($scope.fsGridConfig.filter) {
					contentEL = contentEL.replace(/MODEL_FILTER/g, '| ' + $scope.fsGridConfig.filter);
				} else {
					contentEL = contentEL.replace(/MODEL_FILTER/g, '');
				}
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = contentEL.replace(/REQUIRED_EXPRESSION/g, $scope.fsGridConfig.postPositionRequired);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	//<fs-lookup-multiple ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsLookupMultiple.$inject = ['$templateCache', '$compile', '$parse', '$log', 'modalService', '$filter'];
	function fsLookupMultiple($templateCache, $compile, $parse, $log, modalService, $filter) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', 'fsLookupMultiple', '^^?uiGrid'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var contentEL, content, watcher, watchers = [], log = $log.getInstance('fsLookupMultiple');;
				initComponent(ctrls[1], ctrls[0]);
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				var getter = $parse($scope.fsGridConfig.model);
				$scope.indexOfModel = function (option) {
					var index = -1;
					if (!$scope.ngModel)
						return;
					angular.forEach(getter($scope.ngModel), function (value, key) {
						if (value == option) {
							index = key;
						}
					});
					return index;
				};
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options), gridOptionListener);
					watchers.push(watcher);
				} else {
					buildRemoveCallbackFunction();
					buildOpenCustomDialogsFunction();
				}
				watcher = $scope.$watch($attrs.ngModel, modelChanged);
				watchers.push(watcher);
				function gridOptionListener(options) {
					if (options) {
						$scope.fsGridConfig.options = options;
						buildRemoveCallbackFunction();
						buildOpenCustomDialogsFunction();
					}
				}
				function modelChanged(ngModel) {
					var boundTo;
					if (!$scope.fsGridConfig.options) {
						return;
					}
					boundTo = $scope.fsGridConfig.options.boundTo;
					if (boundTo) {
						if (ngModel == null || ngModel == undefined || ngModel == '') {
							if (angular.isString(boundTo)) {
								$parse(boundTo).assign($scope.ngModel, null);
							} else {
								angular.forEach(boundTo, function (value, key) {
									if (value != $scope.fsGridConfig.model) {
										$parse(value).assign($scope.ngModel, null);
									}
								});
							}
						}
					}
					if ($scope.fsGridConfig.options.callback && $scope.fsGridConfig.options.callback.initOption) {
						if (angular.isString($scope.fsGridConfig.options.callback.initOption)) {
							var callback = $scope.$eval($scope.fsGridConfig.options.callback.initOption);
							if (!callback) {
								log.debug("cannot found the callback function:" + $scope.fsGridConfig.options.callback.initOption);
							} else {
								callback(ngModel, $scope.fsGridConfig.options);
							}
						} else if (angular.isFunction($scope.fsGridConfig.options.callback.initOption)) {
							$scope.fsGridConfig.options.callback.initOption(ngModel, $scope.fsGridConfig.options);
						}
					} else {
						var modelValue = $parse($scope.fsGridConfig.model)($scope.ngModel);
						if (angular.isArray(modelValue)) {
							$scope.fsGridConfig.options.data = [];
							angular.forEach(modelValue, function (value) {
								$scope.fsGridConfig.options.data.push({
									'value': value,
									'label': value
								});
							});
						}
					}
				}
				function buildRemoveCallbackFunction() {
					if ($scope.fsGridConfig.options.callback && $scope.fsGridConfig.options.callback.removeAll && angular.isString($scope.fsGridConfig.options.callback.removeAll)) {
						$scope.removeAllClick = angular.noop;
						watcher = $scope.fsGridConfig.appScope.$watch($scope.fsGridConfig.options.callback.removeAll, function (removeAll) {
							$scope.removeAllClick = function ($event) {
								var promise;
								if (!angular.isFunction(removeAll)) {
									_removeAll();
									return;
								}
								promise = (removeAll || angular.noop)($scope.fsGridConfig.options.data);
								if (promise && angular.isFunction(promise.then)) {
									promise.then(function () {
										_removeAll();
									});
								} else {
									_removeAll();
								}
								$scope.fsGridConfig.appScope.$emit('CONTEXT_MENUE_REMOVE_ALL', $scope.fsGridConfig.options.data);
							};
							initRemoveAllOptions();
						});
						watchers.push(watcher);
					} else if ($scope.fsGridConfig.options.callback && angular.isFunction($scope.fsGridConfig.options.callback.removeAll)) {
						$scope.removeAllClick = function ($event) {
							var promise = $scope.fsGridConfig.options.callback.removeAll($scope.fsGridConfig.options.data);
							if (promise && angular.isFunction(promise.then)) {
								promise.then(function () {
									_removeAll();
								});
							} else {
								_removeAll();
							}
							$scope.fsGridConfig.appScope.$emit('CONTEXT_MENUE_REMOVE_ALL', $scope.fsGridConfig.options.data);
						};
						initRemoveAllOptions();
					} else {
						$scope.removeAllClick = function ($event) {
							_removeAll();
							$scope.fsGridConfig.appScope.$emit('CONTEXT_MENUE_REMOVE_ALL', $scope.fsGridConfig.options.data);
						};
						initRemoveAllOptions();
					}
					function _removeAll() {
						$parse($scope.fsGridConfig.model).assign($scope.ngModel, []);
						$scope.fsGridConfig.options.data = [];
					}
					function initRemoveAllOptions() {
						$scope.removeAllOptions = [
							[
								$filter('translate')('label.global.removeall'),
								$scope.removeAllClick,
								function () {
									return $scope.fsGridConfig.options.data && $scope.fsGridConfig.options.data.length > 0;
								}
							]
						];
					}
					if ($scope.fsGridConfig.options.callback && $scope.fsGridConfig.options.callback.remove && angular.isString($scope.fsGridConfig.options.callback.remove)) {
						$scope.removeClick = angular.noop;
						watcher = $scope.fsGridConfig.appScope.$watch($scope.fsGridConfig.options.callback.remove, function (remove) {
							$scope.removeClick = function ($event) {
								var promise;
								if (!angular.isFunction(remove)) {
									_remove();
									return;
								}
								promise = (remove || angular.noop)($scope.fsGridConfig.fsLookupMultipleModel);
								if (promise && angular.isFunction(promise.then)) {
									promise.then(function () {
										_remove();
									});
								} else {
									_remove();
								}
								$scope.fsGridConfig.appScope.$emit('CONTEXT_MENUE_REMOVE', $scope.fsGridConfig.fsLookupMultipleModel);
							};
							initContextMenuOptions();
						});
						watchers.push(watcher);
					} else if ($scope.fsGridConfig.options.callback && angular.isFunction($scope.fsGridConfig.options.callback.remove)) {
						$scope.removeClick = function ($event) {
							var promise = $scope.fsGridConfig.options.callback.remove($scope.fsGridConfig.fsLookupMultipleModel);
							if (promise && angular.isFunction(promise.then)) {
								promise.then(function () {
									_remove();
								});
							} else {
								_remove();
							}
							$scope.fsGridConfig.appScope.$emit('CONTEXT_MENUE_REMOVE', $scope.fsGridConfig.fsLookupMultipleModel);
						};
						initContextMenuOptions();
					} else {
						$scope.removeClick = function ($event) {
							_remove();
							$scope.fsGridConfig.appScope.$emit('CONTEXT_MENUE_REMOVE', $scope.fsGridConfig.fsLookupMultipleModel);
						};
						initContextMenuOptions();
					}
					function _remove() {
						var i, j, getter = $parse($scope.fsGridConfig.model), list = getter($scope.ngModel) || [], options = $scope.fsGridConfig.options.data, option = $scope.fsGridConfig.fsLookupMultipleModel;
						for (i = 0; i < option.length; i++) {
							for (j = 0; j < list.length; j++) {
								if (option[i] == list[j]) {
									list.splice(j, 1);
									getter.assign($scope.ngModel, angular.copy(list));
									break;
								}
							}
						}
						for (i = 0; i < option.length; i++) {
							for (j = 0; j < options.length; j++) {
								if (option[i] == options[j].value) {
									options.splice(j, 1);
									break;
								}
							}
						}
						$scope.fsGridConfig.fsLookupMultipleModel = [];
					}
					function initContextMenuOptions() {
						$scope._contextMenuOptions = [
							[
								$filter('translate')('label.global.remove'),
								$scope.removeClick,
								function () {
									return $scope.fsGridConfig.fsLookupMultipleModel && $scope.fsGridConfig.fsLookupMultipleModel.length > 0;
								}
							]
						];
					}
				}
				function buildOpenCustomDialogsFunction() {
					var params = {};
					if ($scope.fsGridConfig.options) {
						$scope.fsGridConfig.fsLookupMultipleModel = $scope.fsGridConfig.fsLookupMultipleModel || [];
						$scope.fsGridConfig.options.data = $scope.fsGridConfig.options.data || [];
						$scope.openCustomDialogs = function () {
							angular.forEach($scope.fsGridConfig.options.params, function (parameterExpression, parameterKey) {
								params[parameterKey] = extractParamater(parameterExpression)
							});
							modalService.openCustomDialogs({
								dialogsModule: $scope.fsGridConfig.options.id,
								params: params,
								btnCallback: {
									'ok': okCallback,
									'close': closeCallback
								}
							});
						};
					}
				}
				function extractParamater(parameterExpression) {
					var value, appScope = ($scope.uiGrid ? $scope.uiGrid.appScope : ctrls[0].getGridAppScope());
					if (!angular.isString(parameterExpression)) {
						value = parameterExpression;
					} else if (parameterExpression == '<this') {
						if ($scope.ngModel) {
							value = $scope.ngModel
						} else {
							value = appScope;
						}
					} else if (parameterExpression && parameterExpression.charAt(0) == '<') {
						parameterExpression = parameterExpression.substr(1);
						value = $parse(parameterExpression)($scope.ngModel);
						if (value == undefined) {
							value = $parse(parameterExpression)(appScope);
						}
					} else {
						value = parameterExpression;
					}
					return value;
				}
				function okCallback(lookupResult) {
					var appScope = (ctrls[0].getGridAppScope() || ctrls[2].grid.appScope), option, boundTo = $scope.fsGridConfig.options.boundTo;
					if (!lookupResult || lookupResult.length == 0) {
						modalService.openErrorDialogs({
							messages: ['title.dialogs.info.norecsel']
						});
						return false;
					}
					if (boundTo && angular.isArray(lookupResult)) {
						var getter = $parse($scope.fsGridConfig.model), modelValue = getter($scope.ngModel) || [], option;
						angular.forEach(lookupResult, function (result) {
							option = {};
							angular.forEach(boundTo, function (value, key) {
								if (value == 'value' || value == 'label') {
									option[value] = $parse(key)(result);
								}
							});
							option.label = option.label || option.value;
							modelValue.push(option.value);
							$scope.fsGridConfig.options.data.push(option);
						});
						getter.assign($scope.ngModel, angular.copy(modelValue));
					}
					if ($scope.fsGridConfig.options.callback) {
						if (angular.isString($scope.fsGridConfig.options.callback.ok)) {
							callback = appScope.$eval($scope.fsGridConfig.options.callback.ok);
							if (!callback) {
								log.debug("cannot found the callback function:" + $scope.fsGridConfig.options.callback.ok);
							} else {
								callback(lookupResult, $scope.fsGridConfig.options);
							}
						} else if (angular.isFunction($scope.fsGridConfig.options.callback.ok)) {
							$scope.fsGridConfig.options.callback.ok(lookupResult, $scope.fsGridConfig.options);
						}
					}
					if ($scope.fsGridConfig.options.okEvent) {
						appScope.$emit(($scope.fsGridConfig.model + '_OK').toUpperCase(), lookupResult);
					}
				}
				function closeCallback(lookupResult) {
					var appScope = (ctrls[0].getGridAppScope() || ctrls[2].grid.appScope);
					if ($scope.fsGridConfig.options.callback) {
						if (angular.isString($scope.fsGridConfig.options.callback.close)) {
							callback = appScope.$eval($scope.fsGridConfig.options.callback.close);
							if (!callback) {
								log.debug("cannot found the callback function:" + $scope.fsGridConfig.options.callback.close);
							} else {
								callback(lookupResult);
							}
						} else if (angular.isFunction($scope.fsGridConfig.options.callback.close)) {
							$scope.fsGridConfig.options.callback.close(lookupResult);
						}
					}
					if ($scope.fsGridConfig.options.cancelEvent) {
						appScope.$emit(($scope.fsGridConfig.model + '_CANCEL').toUpperCase(), lookupResult);
					}
				}

				var activeShowExpression = '', labelExpression = '', requiredExpression = '';
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				contentEL = $templateCache.get('fs/layout/template/fsLookupMultiple.html');
				contentEL = contentEL.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = contentEL.replace(/REQUIRED_EXPRESSION/g, $scope.fsGridConfig.postPositionRequired);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	// <fs-buttons fs-buttons-config="vm.buttonGroup"></fs-buttons>
	fsButtons.$inject = ['$templateCache', '$compile', '$parse'];
	function fsButtons($templateCache, $compile, $parse) {
		function getAppScope(scope) {
			if (!scope) {
				return null;
			}
			if (scope.hasOwnProperty('vm') || scope.hasOwnProperty('mainCtrl') || scope.hasOwnProperty('panelController')) {
				return scope;
			} else {
				return getAppScope(scope.$parent);
			}
		}
		return {
			restrict: 'EA',
			scope: true,
			controllerAs: 'fsButtonGroupCtrl',
			controller: ['$scope', '$element', function ($scope, $element) {
				var ctrl = this, appScope = getAppScope($scope);
				$element.addClass('fsButtons');
				ctrl.isActive = function (grid) {
					var active, expression = grid ? grid.active : undefined;
					if (expression == undefined || expression === true) {
						return true;
					} else if (expression === false) {
						return false;
					}
					active = $parse(expression)($scope);
					if (!active && $scope.ngModel !== undefined && grid.model !== undefined && $scope.ngModel[grid.model] !== undefined) {
						delete $scope.ngModel[grid.model];
					}
					return active;
				};
				ctrl.isShow = function (grid) {
					var show, expression = grid ? grid.show : undefined;
					if (expression == undefined || expression === true) {
						return true;
					} else if (expression === false) {
						return false;
					}
					show = $parse(expression)($scope);
					return show;
				};
				ctrl.isReadonly = function (grid) {
					var readonly, expression = grid ? grid.readonly : undefined;
					if (expression === true) {
						return true;
					} else if (expression == undefined || expression === false) {
						return false;
					}
					readonly = $parse(expression)($scope);
					return readonly;
				};
				ctrl.getButtonsScope = function () {
					return $scope;
				};
			}],
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				/*var watchers = [], watcher;
				$scope.$on('$destroy', function() {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				watcher = $scope.$watch($attrs.fsButtonsConfig, function(fsButtonsConfig) {
					if (fsButtonsConfig == $scope.buttonGroup)
						return;
					if (fsButtonsConfig) {
						$scope.fsButtonsConfig = fsButtonsConfig;
					}
				});
				watchers.push(watcher);*/
				var content, contentEL, activeShowExpression = '', labelExpression = '', tootipsExpression = '';
				if ($scope.fsGridConfig) {// this buttonGroup is one of form elements like an input element
					if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
						activeShowExpression = 'ng-if="fsButtonGroupCtrl.isActive(fsGridConfig)" ng-show="fsButtonGroupCtrl.isShow(fsGridConfig)"';
					} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
						activeShowExpression = 'ng-if="fsButtonGroupCtrl.isActive(fsGridConfig)"';
					} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
						activeShowExpression = 'ng-show="fsButtonGroupCtrl.isShow(fsGridConfig)"';
					}
					if ($scope.fsGridConfig.hasOwnProperty('label')) {
						if ($scope.fsGridConfig.showLabel === true) {
							labelExpression = defaultLabel1;
						} else if ($scope.fsGridConfig.showLabel === false) {
							labelExpression = '';
						} else {
							labelExpression = defaultLabel2;
						}
					}
				}
				if ($scope.button && (($scope.button.options && $scope.button.options.hotKey) || $scope.button.tooltips.length)) {
					tootipsExpression = 'uib-tooltip="{{button.options.hotKey.toUpperCase() || button.tooltips[0]|translate }}" tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="button.tooltipPlacement"';
				}
				contentEL = $templateCache.get('fs/layout/template/fsButtons.html');
				contentEL = contentEL.replace(/FS_BUTTONS_CONFIG/g, $attrs.fsButtonsConfig);
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = contentEL.replace(/TOOLTIPS_EXPRESSION/g, tootipsExpression);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	// <fs-button fs-button-config="vm.button" row="row"></fs-button>
	fsButton.$inject = ['$rootScope', '$timeout', '$templateCache', '$compile', '$parse', '$q', 'hotkeys', 'modalService'];
	function fsButton($rootScope, $timeout, $templateCache, $compile, $parse, $q, hotkeys, modalService) {
		function getAppScope(scope) {
			if (!scope) {
				return null;
			}
			if (scope.hasOwnProperty('vm') || scope.hasOwnProperty('mainCtrl') || scope.hasOwnProperty('panelController')) {
				return scope;
			} else {
				return getAppScope(scope.$parent);
			}
		}
		return {
			restrict: 'EA',
			require: ['^?fsButtons', '^^?form', '^?fsGrid', '^^?uiGrid', 'fsButton'],
			scope: {
				button: '<fsButtonConfig',
				uiGrid: '=fsUiGrid',
				uiGridRow: '=fsUiGridRow',
				row: '=fsUiGridRow',
				uiGridCol: '=fsUiGridCol'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var appScope, content, contentEL, clickCallback, fsButtonGroupCtrl = ctrls[0], formCtrl = ctrls[1], fsGridCtrl = ctrls[2], uiGridCtrl = ctrls[3], fsButtonCtrl = ctrls[4], topFormCtrl = getTopFormCtrl(formCtrl);
				$element.addClass('fsButton');
				if ($scope.button.type == 'search') {
					$element.addClass('search');
				}
				if (uiGridCtrl) {
					appScope = uiGridCtrl.grid.appScope;
				} else if (fsGridCtrl) {
					appScope = fsGridCtrl.getGridAppScope();
				} else if (fsButtonGroupCtrl) {
					appScope = getAppScope($scope);
				} else {
					appScope = $scope.button.btnScope;
				}

				$scope.isInButtonGroup = fsButtonGroupCtrl ? true : false;

				if (!appScope.registerSearchCallback) {
					appScope.registerSearchCallback = function (callback, hotkey) {
						appScope.searchCallback = callback;
						appScope.searchCallbackHotKey = hotkey;
					};
				}

				if (!appScope.registerSubmitCallback) {
					appScope.registerSubmitCallback = function (callback) {
						appScope.submitCallback = callback;
					};
				}

				$scope.isSearchCollapsed = function () {
					return appScope.isSearchCollapsed ? appScope.isSearchCollapsed() : false;
				};
				$scope.enabled = function (button) {
					var enabled, expression = button ? button.enabled : undefined;
					if (button.disabled === true) return false;
					if (expression == undefined) {
						return true;
					}
					enabled = $parse(expression)(appScope);
					return enabled;
				};
				$scope.btnClick = function ($event) {
					var rowEntity = null;
					if (ctrls[3]) {
						rowEntity = $scope.uiGridRow.entity;
					} else if (ctrls[0]) {
						rowEntity = ctrls[0].getButtonsScope().ngModel;
					} else if (ctrls[2]) {
						rowEntity = ctrls[2].getGridAppScope().ngModel;
					}
					if (angular.isString($scope.button.options.callback.click)) {
						initButton($scope.button.options.callback.click, 'click');
					}
					return $scope.button.options.callback.click(rowEntity, getOldEntity(rowEntity), $scope.button);
				};
				fsButtonCtrl.isActive = function (grid) {
					if (!fsButtonGroupCtrl || !angular.isFunction(fsButtonGroupCtrl.isActive)) {
						if (!fsGridCtrl || !angular.isFunction(fsGridCtrl.isActive)) {
							return true;
						}
						return fsGridCtrl.isActive(grid);
					}
					return fsButtonGroupCtrl.isActive(grid);
				};
				fsButtonCtrl.isShow = function (grid) {
					if (!fsButtonGroupCtrl || !angular.isFunction(fsButtonGroupCtrl.isShow)) {
						if (!fsGridCtrl || !angular.isFunction(fsGridCtrl.isShow)) {
							return true;
						}
						return fsGridCtrl.isShow(grid);
					}
					return fsButtonGroupCtrl.isShow(grid);
				};
				fsButtonCtrl.isReadonly = function (grid) {
					if (!angular.isFunction(fsButtonGroupCtrl.isReadonly)) {
						if (!angular.isFunction(fsGridCtrl.isReadonly)) {
							return false;
						}
						return fsGridCtrl.isReadonly(grid);
					}
					return fsButtonGroupCtrl.isReadonly(grid);
				};
				$scope.button.tooltips = [];
				if (angular.isArray($scope.button.tooltip)) {
					$scope.button.tooltips = $scope.button.tooltip;
				} else if (angular.isString($scope.button.tooltip)) {
					$scope.button.tooltips[0] = $scope.button.tooltip;
				}
				$scope.button.authorization = appScope ? (appScope.authorization || ((appScope.$stateParams) ? appScope.$stateParams.authorization : null)) : null;
				$scope.button.permission = $scope.button.permission || '*';
				$scope.button.tooltipPlacement = $scope.button.tooltipPlacement || 'top-left';
				$scope.button.type = $scope.button.type || 'submit';
				$scope.button.btnClass = $scope.button.btnClass || "btn-default";
				$scope.button.options.callback = $scope.button.options.callback || {
					'click': angular.noop
				};
				angular.forEach($scope.button.options.callback, function (callback, event) {
					initButton(callback, event);
				});
				function initButton(callback, event) {
					if ($scope.button.type == 'search' && event == 'click') {
						$scope.button.options.callback[event] = function () {
							disableButton();
							appScope.toggleSearchGroupCollapsed();
						};
						appScope.registerSearchCallback(btnCallback, $scope.button.options.hotKey);
						if ($scope.button.options.hotKey) {
							var permissions, i, hasPermission = false;
							permissions = $scope.button ? ($scope.button.permission || '*') : '*';
							if (permissions == '*') hasPermission = true;
							permissions = permissions.split(',');
							angular.forEach(permissions, function (value, key) {
								permissions[key] = value.trim();
							});
							for (i = 0; i < permissions.length; i++) {
								if (!$scope.button || ($scope.button.authorization && $scope.button.authorization[permissions[i]])) {
									hasPermission = true;
								}
							}
							if (hasPermission) {
								hotkeys.bindTo($scope).add({
									combo: $scope.button.options.hotKey,
									description: $scope.button.options.hotKey,
									allowIn: ['RADIO', 'INPUT', 'SELECT', 'TEXTAREA'],
									callback: btnCallback
								});
							}
							$scope.button.options.hotKey = null;
						}
						function btnCallback() {
							if (!$scope.enabled($scope.button)) {
								return;
							}
							if (disableButton()) {
								return;
							}
							var resultCallback = ($parse(callback)(appScope) || angular.noop);
							if (topFormCtrl) {
								topFormCtrl.$setSubmitted();
								delete topFormCtrl.formValidation;
								if (topFormCtrl.$invalid && getFormValidateMessages([topFormCtrl])) {
									if (topFormCtrl.errorMessagesDialog && topFormCtrl.errorMessagesDialog.close) {
										topFormCtrl.errorMessagesDialog.close();
									}
									topFormCtrl.errorMessagesDialog = showFormErrorMessages(topFormCtrl);
									return false;
								} else {
									$rootScope.disableUnloadCheck();
									excuteCallback(resultCallback, arguments);
								}
							} else {
								$rootScope.disableUnloadCheck();
								excuteCallback(resultCallback, arguments);
							}
						}
					} else {
						if (angular.isString(callback)) {
							$scope.button.options.callback[event] = function () {
								disableButton();
								excuteCallback(callback, arguments);
							};
						}
					}
					if ($scope.button.type == 'submit' && event == 'click') {
						clickCallback = $scope.button.options.callback[event];
						if (topFormCtrl) {
							$scope.button.options.callback[event] = function () {
								disableButton();
								var promises = validateFormData();
								var args = arguments;
								var defer = $q.defer();
								$q.all(promises).then(function () {
									topFormCtrl.$setSubmitted();
									delete topFormCtrl.formValidation;
									if (topFormCtrl.$invalid && getFormValidateMessages([topFormCtrl])) {
										if (topFormCtrl.errorMessagesDialog && topFormCtrl.errorMessagesDialog.close) {
											topFormCtrl.errorMessagesDialog.close();
										}
										topFormCtrl.errorMessagesDialog = showFormErrorMessages(topFormCtrl);
										defer.resolve(false);
									} else {
										defer.resolve();
										$rootScope.disableUnloadCheck();
										excuteCallback(callback, args);
									}
								});
								return defer.promise;
							};
						} else {
							$scope.button.options.callback[event] = function () {
								disableButton();
								$rootScope.disableUnloadCheck();
								excuteCallback(callback, arguments);
							};
						}
						appScope.registerSubmitCallback($scope.button.options.callback[event]);
					} else if ($scope.button.type == 'reset') {
						$scope.button.options.callback = $scope.button.options.callback || {};
						clickCallback = $scope.button.options.callback[event];
						$scope.button.options.callback[event] = function () {
							disableButton();
							var formCtrls = getFormCtrls(formCtrl);
							if (topFormCtrl && topFormCtrl._originalNgModelKey) {
								if (topFormCtrl._originalNgModel) {
									angular.copy(topFormCtrl._originalNgModel, $scope.$eval(topFormCtrl._originalNgModelKey));
								} else {
									$parse(topFormCtrl._originalNgModelKey).assign($scope, topFormCtrl._originalNgModel);
								}
							}
							angular.forEach(formCtrls, function (formController) {
								formController.$setPristine();
								formController.$setUntouched();
							});
							excuteCallback(callback, arguments);
						};
					}
					if ($scope.button.options.hotKey && $scope.button.type != 'search') {
						var permissions, i, hasPermission = false;
						permissions = $scope.button ? ($scope.button.permission || '*') : '*';
						if (permissions == '*') hasPermission = true;
						permissions = permissions.split(',');
						angular.forEach(permissions, function (value, key) {
							permissions[key] = value.trim();
						});
						for (i = 0; i < permissions.length; i++) {
							if (!$scope.button || ($scope.button.authorization && $scope.button.authorization[permissions[i]])) {
								hasPermission = true;
							}
						}
						function btnCallback() {
							if (!$scope.enabled($scope.button)) {
								return;
							}
							if (disableButton()) {
								return;
							}
							var resultCallback = ($scope.button.options.callback && $scope.button.options.callback.click ? $scope.button.options.callback.click : angular.noop);
							excuteCallback(resultCallback, arguments);
						};
						if (hasPermission) {
							hotkeys.bindTo($scope).add({
								combo: $scope.button.options.hotKey,
								description: $scope.button.options.hotKey,
								allowIn: ['RADIO', 'INPUT', 'SELECT', 'TEXTAREA'],
								callback: btnCallback
							});
						}
					}
				}
				function excuteCallback(callback, args) {
					var fn = $parse(callback)(appScope);
					if (!angular.isFunction(fn)) {
						fn = angular.noop;
					}
					if (args.length == 0) {
						fn();
					} else if (args.length == 1) {
						fn(args[0]);
					} else if (args.length == 2) {
						fn(args[0], args[1]);
					} else if (args.length == 3) {
						fn(args[0], args[1], args[2]);
					} else if (args.length == 4) {
						fn(args[0], args[1], args[2], args[3]);
					} else if (args.length == 5) {
						fn(args[0], args[1], args[2], args[3], args[4]);
					}
				}
				function getOldEntity(entity) {
					var oldData, key;
					if (appScope && ctrls[3]) {
						oldData = appScope.$eval(uiGridCtrl.grid.options.oldData);
					} else if (appScope) {
						oldData = appScope.verifyModel;
					}
					if (oldData) {
						for (data in oldData) {
							if (entity.getPrimaryKey == data.getPrimaryKey) {
								return data;
							}
						}
					}
					return null;
				}
				function disableButton() {
					if ($scope.button.disabled) {
						return false;
					}
					$scope.button.disabled = true;
					$timeout(function () {
						$scope.button.disabled = false;
						$scope.$digest();
					}, 500, false);
				}
				function validateFormData() {
					var promises = [], defered;
					if (topFormCtrl && topFormCtrl.tabLoaded) {
						if (!topFormCtrl.formValidation) {
							topFormCtrl.formValidation = new Array(topFormCtrl.tabLoaded.length);
						}
						angular.forEach(topFormCtrl.tabLoaded, function (value, index) {
							if (value === false) {
								topFormCtrl.tabLoaded[index] = true;
								defered = $q.defer();
								topFormCtrl.formValidation[index] = {
									validated: false,
									defered: defered
								};
								promises.push(topFormCtrl.formValidation[index].defered.promise);
							} else {
								promises.push($q.resolve());
							}
						});
					} else {
						promises.push($q.resolve());
					}
					return promises;
				}
				function showFormErrorMessages(topFormCtrl) {
					var dialogsInstance, errors = getFormValidateMessages([topFormCtrl]), errorMessageObj = {};
					if (errors) {
						dialogsInstance = modalService.openErrorDialogs({
							'title': '',
							'messages': errors
						});
					}
					return dialogsInstance;
				}
				function getFormValidateMessages(formCtrls) {
					var key1, key2, repeat = false, errors = {}, unrepeatErrors = {};
					angular.forEach(formCtrls, function (formCtrl) {
						if (formCtrl.validateErrors) {
							angular.merge(errors, formCtrl.validateErrors);
						}
						if (formCtrl._formCtrls) {
							angular.merge(errors, getFormValidateMessages(formCtrl._formCtrls));
						}
					});
					for (key1 in errors) {
						repeat = false
						for (key2 in errors) {
							if (key1 != key2 && errors[key1] == errors[key2]) {
								repeat = true;
								delete errors[key1];
								break;
							}
						}
						if (!repeat) {
							unrepeatErrors[key2] = errors[key2];
						}
					}
					return angular.equals({}, errors) ? null : errors;
				}
				var labelExpression = '', activeShowExpression = '', tootipsExpression = '', searchBtnIconExpression = '';
				if ($scope.button) {// this buttonGroup is one of form elements like an input element
					if ($scope.button.hasOwnProperty('active') && $scope.button.hasOwnProperty('show')) {
						activeShowExpression = 'ng-if="formComponentCtrl.isActive(button)" ng-show="formComponentCtrl.isShow(button)"';
					} else if ($scope.button.hasOwnProperty('active')) {
						activeShowExpression = 'ng-if="formComponentCtrl.isActive(button)"';
					} else if ($scope.button.hasOwnProperty('show')) {
						activeShowExpression = 'ng-show="formComponentCtrl.isShow(button)"';
					}
					if ($scope.button.hasOwnProperty('label')) {
						if ($scope.button.showLabel === true) {
							labelExpression = defaultLabel1;
						} else if ($scope.button.showLabel === false) {
							labelExpression = '';
						} else {
							labelExpression = defaultLabel2;
						}
					}
				}
				if ($scope.button && (($scope.button.options && $scope.button.options.hotKey) || $scope.button.tooltips.length)) {
					tootipsExpression = 'uib-tooltip="{{button.options.hotKey.toUpperCase() || button.tooltips[0]|translate }}" tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="button.tooltipPlacement"';
				}
				if ($scope.button && $scope.button.type == 'search') {
					searchBtnIconExpression = '<span class="icon" ng-class="{\'icon-up\': !isSearchCollapsed(), \'icon-down\': isSearchCollapsed()}"></span>';
				}
				contentEL = $templateCache.get('fs/layout/template/fsButton.html');
				contentEL = contentEL.replace(/FS_BUTTONS_CONFIG/g, $attrs.fsButtonsConfig);
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = contentEL.replace(/TOOLTIPS_EXPRESSION/g, tootipsExpression);
				contentEL = contentEL.replace(/SEARCH_BTN_ICON_EXPRESSION/g, searchBtnIconExpression);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	// <fs-card fs-grid-config="fsGridConfig"/>
	fsCard.$inject = ['$templateCache', '$compile', '$parse'];
	function fsCard($templateCache, $compile, $parse) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', 'fsCard', '?^fsRadioCard'],
			scope: {
				ngModel: '=ngModel',
				fsVerifyModel: '<fsVerifyModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var contentEL, content, watcher, watched, watchers = [], fsRadioCardCtrl = ctrls[2];
				if (fsRadioCardCtrl) {
					$scope.hasFsRadioCard = true;
				} else {
					$scope.hasFsRadioCard = false;
				}
				initComponent(ctrls[1], ctrls[0]);
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionsListener);
					watchers.push(watcher);
				} else if ($scope.fsGridConfig.options) {
					if (angular.isString($scope.fsGridConfig.options.data)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.data, gridOptionDataListener);
						watchers.push(watcher);
					}
				}
				function gridOptionsListener(options) {
					$scope.fsGridConfig.options = options;
					if (!watched && $scope.fsGridConfig.options && angular.isString($scope.fsGridConfig.options.data)) {
						watched = true;
						watchers.push(watchGridScope(ctrls[0], $scope.fsGridConfig.options.data, gridOptionDataListener));
					}
				}
				function gridOptionDataListener(data) {
					$parse('data').assign($scope.fsGridConfig.options, data);
				}
				contentEL = $templateCache.get('fs/layout/template/fsCard.html');
				contentEL = contentEL.replace(/CARD_TEMPLATE/g, $scope.fsGridConfig.template || $templateCache.get($scope.fsGridConfig.templateUrl) || '<div class="panel-body">need list item template</div>');
				contentEL = contentEL.replace(/CARD_MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				//contentEL = $scope.fsGridConfig.template || $templateCache.get($scope.fsGridConfig.templateUrl || 'fs/layout/template/fsCard.html');

				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);
			}
		};
	}

	// <fs-list-group fs-grid-config="fsGridConfig"/>
	fsListGroup.$inject = ['$templateCache', '$compile', '$parse'];
	function fsListGroup($templateCache, $compile, $parse) {
		return {
			restrict: 'EA',
			require: ['^fsGrid', 'fsListGroup'],
			scope: {
				ngModel: '=ngModel',
				fsGridConfig: '<fsGridConfig'
			},
			controller: function () { },
			controllerAs: 'formComponentCtrl',
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var contentEL, content, watcher, watched, watchers = [], scrollerbar;
				initComponent(ctrls[1], ctrls[0]);
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionsListener);
					watchers.push(watcher);
				} else if ($scope.fsGridConfig.options) {
					if (angular.isString($scope.fsGridConfig.options.data)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.data, gridOptionDataListener);
						watchers.push(watcher);
					}
				} else {
					initSelectCallback();
					buildListConfigs();
				}
				ctrls[1].onScrollYEnd = function (position) {
					if (ctrls[1].scrollEndCallback && position.scrollTop > 0) {
						ctrls[1].scrollEndCallback(position);
					}
				};
				ctrls[1].selectItem = function (item) {
					var getter;
					if ($scope.fsGridConfig.model) {
						getter = $parse($scope.fsGridConfig.model);
						if (getter($scope.ngModel) != item) {
							getter.assign($scope.ngModel, item);
							if (ctrls[1].selectCallback) {
								ctrls[1].selectCallback(item);
							}
						}
					} else if (ctrls[1].selectCallback) {
						ctrls[1].selectCallback(item);
					}
				};
				function gridOptionsListener(options) {
					$scope.fsGridConfig.options = options;
					if (!watched && $scope.fsGridConfig.options && angular.isString($scope.fsGridConfig.options.data)) {
						watched = true;
						watchers.push(watchGridScope(ctrls[0], $scope.fsGridConfig.options.data, gridOptionDataListener));
					} else {
						initSelectCallback();
						buildListConfigs();
					}
				}
				function gridOptionDataListener(data) {
					$parse('data').assign($scope.fsGridConfig.options, data);
					initSelectCallback();
					buildListConfigs();
				}
				function initSelectCallback() {
					if ($scope.fsGridConfig && $scope.fsGridConfig.options && $scope.fsGridConfig.options.callback) {
						if (angular.isString($scope.fsGridConfig.options.callback.click)) {
							ctrls[1].selectCallback = ($scope.fsGridConfig.appScope).$eval($scope.fsGridConfig.options.callback.click);
						} else if (angular.isFunction($scope.fsGridConfig.options.callback.click)) {
							ctrls[1].selectCallback = $scope.fsGridConfig.options.callback.click;
						}

						if (angular.isString($scope.fsGridConfig.options.callback.scrollEnd)) {
							ctrls[1].scrollEndCallback = ($scope.fsGridConfig.appScope).$eval($scope.fsGridConfig.options.callback.scrollEnd);
						} else if (angular.isFunction($scope.fsGridConfig.options.callback.scrollEnd)) {
							ctrls[1].scrollEndCallback = $scope.fsGridConfig.options.callback.scrollEnd;
						}
					}
				}
				function buildListConfigs() {
					$scope.fsGridConfig.listConfigs = [];
					angular.forEach($scope.fsGridConfig.options.data, function (data, index) {
						$scope.fsGridConfig.listConfigs.push(data);
					});
				}
				contentEL = $templateCache.get('fs/layout/template/fsListGroup.html');
				contentEL = contentEL.replace(/LISTITEM_TEMPLATE/g, $scope.fsGridConfig.template || $templateCache.get($scope.fsGridConfig.templateUrl) || '<div class="panel-body">need list item template</div>');
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);

				var activeShowExpression = '', labelExpression = '', requiredExpression = '';
				if ($scope.fsGridConfig.hasOwnProperty('active') && $scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('active')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isActive(fsGridConfig)"';
				} else if ($scope.fsGridConfig.hasOwnProperty('show')) {
					activeShowExpression = 'ng-show="formComponentCtrl.isShow(fsGridConfig)"';
				}
				if ($scope.fsGridConfig.hasOwnProperty('label')) {
					if ($scope.fsGridConfig.showLabel === true) {
						labelExpression = defaultLabel1;
					} else if ($scope.fsGridConfig.showLabel === false) {
						labelExpression = '';
					} else {
						labelExpression = defaultLabel2;
					}
				}
				contentEL = $templateCache.get('fs/layout/template/fsListGroup.html');
				contentEL = contentEL.replace(/LISTITEM_TEMPLATE/g, $scope.fsGridConfig.template || $templateCache.get($scope.fsGridConfig.templateUrl) || '<div class="panel-body">need list item template</div>');
				contentEL = contentEL.replace(/IS_ACTIVE_SHOW_EXPRESSION/g, activeShowExpression);
				contentEL = contentEL.replace(/LABEL_EXPRESSION/g, labelExpression);
				contentEL = contentEL.replace(/REQUIRED_EXPRESSION/g, $scope.fsGridConfig.postPositionRequired);
				contentEL = angular.element(contentEL);
				content = $compile(contentEL)($scope);
				// Prevent jQuery cache memory leak (template is now redundant after linking)
				contentEL.remove();
				$element.append(content);

				/*scrollerbar = angular.element($element[0].querySelector('.scroller'));
				scrollerbar.css({
					"max-height" : $scope.fsGridConfig.maxHeight + 'px',
					"min-height" : ($scope.fsGridConfig.minHeight || 1) + 'px'
				});*/
			}
		};
	}

	fsGridFilter.$inject = ['$parse'];
	function fsGridFilter($parse) {
		return {
			restrict: 'A',
			require: ['ngModel'],
			/*
			 * scope : { filter : '<fsGridFilter' },
			 */
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var watcher, watchers = [], ngModelCtrl = ctrls[0];
				function getModelDisplayValue(model, filter) {
					var parse;
					if (filter) {
						parse = $parse(model + ' | ' + filter);
					} else {
						parse = $parse(model);
					}
					return parse($scope);
				}

				watcher = $scope.$watch($attrs.fsGridFilter, function (filter) {
					if (filter && "zeroEmpty" != filter && filter != 'moneyFormatWithCcy') {
						ngModelCtrl.$formatters.push(function (input) {
							var value = (input || (input === 0)) ? getModelDisplayValue($attrs.ngModel, filter) : input;
							return value;
						});
						$element.val(getModelDisplayValue($attrs.ngModel, filter));
					}
				});
				watchers.push(watcher);

				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
			}
		};
	}

	fsGridPrefix.$inject = [];
	function fsGridPrefix() {
		return {
			restrict: 'A',
			require: ['ngModel'],
			scope: {
				prefix: '<fsGridPrefix'
			},
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var watcher, watchers = [], ngModelCtrl = ctrls[0];
				watcher = $scope.$watch('prefix', function (prefix) {
					if (prefix) {
						ngModelCtrl.$parsers.push(function (input) {
							var value = input ? prefix + input : input;
							$element.val(input);
							return value;
						});
						ngModelCtrl.$formatters.push(function (output) {
							output = output ? output.replace(prefix, '') : output;
							return output;
						});
					}
				});
				watchers.push(watcher);

				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
			}
		};
	}

	fsGridFilter.$inject = ['$parse'];
	function fsGridFilter($parse) {
		return {
			restrict: 'A',
			require: ['ngModel'],
			/*
			 * scope : { filter : '<fsGridFilter' },
			 */
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var watcher, watchers = [], ngModelCtrl = ctrls[0];
				function getModelDisplayValue(model, filter) {
					var parse;
					if (filter) {
						parse = $parse(model + ' | ' + filter);
					} else {
						parse = $parse(model);
					}
					return parse($scope);
				}

				watcher = $scope.$watch($attrs.fsGridFilter, function (filter) {
					if (filter && "zeroEmpty" != filter && filter != 'moneyFormatWithCcy') {
						ngModelCtrl.$formatters.push(function (input) {
							var value = (input || (input === 0)) ? getModelDisplayValue($attrs.ngModel, filter) : input;
							return value;
						});
						$element.val(getModelDisplayValue($attrs.ngModel, filter));
					}
				});
				watchers.push(watcher);

				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
			}
		};
	}

	fsGridPrefix.$inject = [];
	function fsGridPrefix() {
		return {
			restrict: 'A',
			require: ['ngModel'],
			scope: {
				prefix: '<fsGridPrefix'
			},
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var watcher, watchers = [], ngModelCtrl = ctrls[0];
				watcher = $scope.$watch('prefix', function (prefix) {
					if (prefix) {
						ngModelCtrl.$parsers.push(function (input) {
							var value = input ? prefix + input : input;
							$element.val(input);
							return value;
						});
						ngModelCtrl.$formatters.push(function (output) {
							output = output ? output.replace(prefix, '') : output;
							return output;
						});
					}
				});
				watchers.push(watcher);

				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
			}
		};
	}

	fsGridFilter.$inject = ['$parse'];
	function fsGridFilter($parse) {
		return {
			restrict: 'A',
			require: ['ngModel'],
			/*scope : {
				filter : '<fsGridFilter'
			},*/
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var watcher, watchers = [], ngModelCtrl = ctrls[0];
				function getModelDisplayValue(model, filter) {
					var parse;
					if (filter) {
						parse = $parse(model + ' | ' + filter);
					} else {
						parse = $parse(model);
					}
					return parse($scope);
				}

				watcher = $scope.$watch($attrs.fsGridFilter, function (filter) {
					if (filter) {
						ngModelCtrl.$formatters.push(function (input) {
							var value = input ? getModelDisplayValue($attrs.ngModel, filter) : input;
							return value;
						});
						$element.val(getModelDisplayValue($attrs.ngModel, filter));
					}
				});
				watchers.push(watcher);

				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
			}
		};
	}

	fsGridPrefix.$inject = [];
	function fsGridPrefix() {
		return {
			restrict: 'A',
			require: ['ngModel'],
			scope: {
				prefix: '<fsGridPrefix'
			},
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var watcher, watchers = [], ngModelCtrl = ctrls[0];
				watcher = $scope.$watch('prefix', function (prefix) {
					if (prefix) {
						ngModelCtrl.$parsers.push(function (input) {
							var value = input ? prefix + input : input;
							$element.val(input);
							return value;
						});
						ngModelCtrl.$formatters.push(function (output) {
							output = output ? output.replace(prefix, '') : output;
							return output;
						});
					}
				});
				watchers.push(watcher);

				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
			}
		};
	}

	// <div fs-ui-grid-compare="verifyData" fs-ui-grid="grid" fs-ui-grid-row="row" />
	fsUiGridCompare.$inject = ['$parse', '$filter'];
	function fsUiGridCompare($parse, $filter) {
		return {
			priority: -300,
			restrict: 'A',
			require: ['^^uiGrid', 'fsUiGridCompare'],
			scope: false,
			bindToController: {
				verifyData: '@fsUiGridCompare',
				uiGrid: '=fsUiGrid',
				uiGridRow: '=fsUiGridRow',
				row: '=fsUiGridRow',
				uiGridCol: '=fsUiGridCol'
			},
			controller: function () { },
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var watcher, watchers = [], uiGridCtrl = ctrls[0], fsUiGridCompareCtrl = ctrls[1];
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				watcher = $scope.$watch($attrs.fsUiGridCompare, function (verifyModelProperty) {
					if (verifyModelProperty) {
						watchers.push(uiGridCtrl.grid.appScope.$watch(verifyModelProperty, function (verifyModel) {
							var oldRowEntity;
							if (verifyModel) {
								fsUiGridCompareCtrl.verifyModel = verifyModel;
								oldRowEntity = getVerifyModel(fsUiGridCompareCtrl.uiGridRow.entity, fsUiGridCompareCtrl.verifyModel);
								verifyCompare(fsUiGridCompareCtrl, fsUiGridCompareCtrl.uiGridRow.entity, oldRowEntity);
							}
						}));
					}
				});
				watchers.push(watcher);
			}
		};
		function verifyCompare(fsUiGridCompareCtrl, rowEntity, oldRowEntity) {
			var value, oldValue, tooltip, compareResult;
			if (!rowEntity) {
				return;
			}
			if (!fsUiGridCompareCtrl.uiGridRow.compareResult) {
				compareResult = {
					fields: []
				};
				if (rowEntity.dataStatus == 'D') {
					compareResult.isDeleted = true;
				} else if (oldRowEntity) {
					angular.forEach(fsUiGridCompareCtrl.uiGrid.columns, function (column) {
						if (column.colDef.comparable === false || !column.colDef.model) {
							return;
						}
						value = $parse(column.colDef.model)(rowEntity);
						oldValue = $parse(column.colDef.model)(oldRowEntity);
						if (!angular.equals(value, oldValue)) {
							compareResult.isUpdated = true;
							compareResult.fields[column.colDef.model] = {
								'hasChanged': true
							};
							if (column.colDef.type == 'sensitive') {
								tooltip = [formatService.formatSst(oldValue, column.colDef.options)];
							} else if (column.colDef.filter) {
								tooltip = [$parse(column.colDef.model + ' | ' + column.colDef.filter)(oldRowEntity)];
							} else if (column.colDef.type == 'select' || column.colDef.type == 'radio' || column.colDef.type == 'checkbox') {
								var tooltip = oldValue;
								if (column.colDef.options) {
									if (angular.isString(column.colDef.options)) {
										column.colDef.options = $parse(column.colDef.options)(fsUiGridCompareCtrl.uiGrid.appScope);
									}
									if (angular.isString(column.colDef.options.data)) {
										column.colDef.options.data = $parse(column.colDef.options.data)(fsUiGridCompareCtrl.uiGrid.appScope);
									}
									for (var i = 0; i < column.colDef.options.data.length; i++) {
										var obj = column.colDef.options.data[i];
										if (obj.value == oldValue) {
											tooltip = $filter('translate')(obj.label);
											break;
										}
									}
								}
								compareResult.tooltip = [tooltip];
							} else if (angular.isDate(oldValue)) {
								if (column.colDef.type == 'date') {
									tooltip = [formatService.formatDate(oldValue, 'date')];
								} else if (column.colDef.type == 'datetime') {
									tooltip = [formatService.formatDate(oldValue, 'timestamp')];
								} else if (column.colDef.type == 'time') {
									tooltip = [formatService.formatDate(oldValue, 'HH:SS')];
								} else {
									tooltip = [formatService.formatDate(oldValue, 'longTimestamp')];
								}
							} else {
								tooltip = [angular.isObject(oldValue) ? angular.toJson(oldValue) : oldValue];
							}
							compareResult.fields[column.colDef.model].tooltip = tooltip;
						}
					});
				} else {
					compareResult.isCreated = true;
					angular.forEach(fsUiGridCompareCtrl.uiGrid.columns, function (column) {
						compareResult.fields[column.colDef.model] = {
							'hasChanged': true
						};
					});
				}
				fsUiGridCompareCtrl.uiGridRow.compareResult = compareResult;
			}
		}
		function getVerifyModel(rowEntity, verifyModel) {
			var i = 0;
			if (angular.isArray(verifyModel)) {
				for (i = 0; i < verifyModel.length; i++) {
					if (verifyModel[i].primaryKey == rowEntity.primaryKey) {
						return verifyModel[i];
					}
				}
			}
		}
	}

	// <div fs-ui-grid-compare="verifyData" fs-ui-grid="grid" fs-ui-grid-row="row" />
	fsUiGridCompare.$inject = ['$parse', '$filter'];
	function fsUiGridCompare($parse, $filter) {
		return {
			priority: -300,
			restrict: 'A',
			require: ['^^uiGrid', 'fsUiGridCompare'],
			scope: false,
			bindToController: {
				verifyData: '@fsUiGridCompare',
				uiGrid: '=fsUiGrid',
				uiGridRow: '=fsUiGridRow',
				row: '=fsUiGridRow',
				uiGridCol: '=fsUiGridCol'
			},
			controller: function () { },
			link: function ($scope, $element, $attrs, ctrls, $transclude) {
				var watcher, watchers = [], uiGridCtrl = ctrls[0], fsUiGridCompareCtrl = ctrls[1];
				$scope.$on('$destroy', function () {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				watcher = $scope.$watch($attrs.fsUiGridCompare, function (verifyModelProperty) {
					if (verifyModelProperty) {
						watchers.push(uiGridCtrl.grid.appScope.$watch(verifyModelProperty, function (verifyModel) {
							var oldRowEntity;
							if (verifyModel) {
								fsUiGridCompareCtrl.verifyModel = verifyModel;
								oldRowEntity = getVerifyModel(fsUiGridCompareCtrl.uiGridRow.entity, fsUiGridCompareCtrl.verifyModel);
								verifyCompare(fsUiGridCompareCtrl, fsUiGridCompareCtrl.uiGridRow.entity, oldRowEntity);
							}
						}));
					}
				});
				watchers.push(watcher);
			}
		};
		function verifyCompare(fsUiGridCompareCtrl, rowEntity, oldRowEntity) {
			var value, oldValue, tooltip, compareResult;
			if (!rowEntity) {
				return;
			}
			if (!fsUiGridCompareCtrl.uiGridRow.compareResult) {
				compareResult = {
					fields: []
				};
				if (rowEntity.dataStatus == 'D') {
					compareResult.isDeleted = true;
				} else if (oldRowEntity) {
					angular.forEach(fsUiGridCompareCtrl.uiGrid.columns, function (column) {
						if (column.colDef.comparable === false || !column.colDef.model) {
							return;
						}
						value = $parse(column.colDef.model)(rowEntity);
						oldValue = $parse(column.colDef.model)(oldRowEntity);
						if (!angular.equals(value, oldValue)) {
							compareResult.isUpdated = true;
							compareResult.fields[column.colDef.model] = {
								'hasChanged': true
							};
							if (column.colDef.type == 'sensitive') {
								tooltip = [formatService.formatSst(oldValue, column.colDef.options)];
							} else if (column.colDef.filter) {
								tooltip = [$parse(column.colDef.model + ' | ' + column.colDef.filter)(oldRowEntity)];
							} else if (column.colDef.type == 'select' || column.colDef.type == 'radio' || column.colDef.type == 'checkbox') {
								var tooltip = oldValue;
								if (column.colDef.options) {
									if (angular.isString(column.colDef.options)) {
										column.colDef.options = $parse(column.colDef.options)(fsUiGridCompareCtrl.uiGrid.appScope);
									}
									if (angular.isString(column.colDef.options.data)) {
										column.colDef.options.data = $parse(column.colDef.options.data)(fsUiGridCompareCtrl.uiGrid.appScope);
									}
									for (var i = 0; i < column.colDef.options.data.length; i++) {
										var obj = column.colDef.options.data[i];
										if (obj.value == oldValue) {
											tooltip = $filter('translate')(obj.label);
											break;
										}
									}
								}
								compareResult.tooltip = [tooltip];
							} else if (angular.isDate(oldValue)) {
								if (column.colDef.type == 'date') {
									tooltip = [formatService.formatDate(oldValue, 'date')];
								} else if (column.colDef.type == 'datetime') {
									tooltip = [formatService.formatDate(oldValue, 'timestamp')];
								} else if (column.colDef.type == 'time') {
									tooltip = [formatService.formatDate(oldValue, 'HH:MM:SS')];
								} else {
									tooltip = [formatService.formatDate(oldValue, 'longTimestamp')];
								}
							} else {
								tooltip = [angular.isObject(oldValue) ? angular.toJson(oldValue) : oldValue];
							}
							compareResult.fields[column.colDef.model].tooltip = tooltip;
						}
					});
				} else {
					compareResult.isCreated = true;
					angular.forEach(fsUiGridCompareCtrl.uiGrid.columns, function (column) {
						compareResult.fields[column.colDef.model] = {
							'hasChanged': true
						};
					});
				}
				fsUiGridCompareCtrl.uiGridRow.compareResult = compareResult;
			}
		}
		function getVerifyModel(rowEntity, verifyModel) {
			var i = 0;
			if (angular.isArray(verifyModel)) {
				for (i = 0; i < verifyModel.length; i++) {
					if (verifyModel[i].primaryKey == rowEntity.primaryKey) {
						return verifyModel[i];
					}
				}
			}
		}
	}

	fsScrollbar.$inject = ['$parse', '$window'];
	function fsScrollbar($parse, $window) {
		var fsScrollbarOptions = ['handlers', 'wheelSpeed', 'wheelPropagation', 'swipeEasing', 'minScrollbarLength', 'maxScrollbarLength', 'scrollingThreshold', 'useBothWheelAxes', 'suppressScrollX',
			'suppressScrollY', 'scrollXMarginOffset', 'scrollYMarginOffset',];

		return {
			restrict: 'E',
			transclude: true,
			template: '<div ng-transclude class="open" ng-click="clickEvent($event)"></div>',
			replace: true,
			link: function ($scope, $elem, $attr) {
				var jqWindow = angular.element($window);
				var options = {};
				var perfect;
				if ($scope.fsGridConfig) {
					$elem.css({
						'max-height': ($scope.fsGridConfig.options.maxHeight || 450) + 'px',
						'min-height': ($scope.fsGridConfig.options.minHeight || 0) + 'px'
					});
				}
				for (var i = 0, l = fsScrollbarOptions.length; i < l; i++) {
					var opt = fsScrollbarOptions[i];

					if ($attr[opt] !== undefined) {
						options[opt] = $parse($attr[opt])();
					}
				}
				$scope.clickEvent = function ($event) {
					var element = $event.srcElement || $event.target;
					element = angular.element(element);
					if (element && (element.hasClass('ps__rail-x') || element.hasClass('ps__rail-y') || element.hasClass('ps__thumb-x') || element.hasClass('ps__thumb-y'))) {
						$event.stopPropagation();
					}
				};
				$scope.$evalAsync(function () {
					perfect = new PerfectScrollbar($elem[0], options);
					var onScrollHandler = $parse($attr.onScroll)($scope);
					var onScrollYEndHandler = $parse($attr.onScrollYEnd)($scope);
					if (onScrollYEndHandler) {
						$elem.on('ps-y-reach-end', function () {
							var scrollTop = $elem.prop('scrollTop');
							var scrollHeight = $elem.prop('scrollHeight') - $elem[0].clientHeight;
							var scrollLeft = $elem.prop('scrollLeft');
							var scrollWidth = $elem.prop('scrollWidth') - $elem[0].clientWidth;

							onScrollYEndHandler({
								scrollTop: scrollTop,
								scrollHeight: scrollHeight,
								scrollLeft: scrollLeft,
								scrollWidth: scrollWidth
							});
						});
					}
					if (onScrollHandler) {
						$elem.on('scroll', function () {
							var scrollTop = $elem.prop('scrollTop');
							var scrollHeight = $elem.prop('scrollHeight') - $elem[0].clientHeight;
							var scrollLeft = $elem.prop('scrollLeft');
							var scrollWidth = $elem.prop('scrollWidth') - $elem[0].clientWidth;

							$scope.$apply(function () {
								onScrollHandler({
									scrollTop: scrollTop,
									scrollHeight: scrollHeight,
									scrollLeft: scrollLeft,
									scrollWidth: scrollWidth
								});
							});
						});
					}
				});

				// Automatically update when content height changes
				$scope.$watch(function () {
					return $elem.prop('scrollHeight');
				}, function (newValue, oldValue) {
					if (newValue) {
						update('contentSizeChange');
					}
				});

				function update(event) {
					$scope.$evalAsync(function () {
						if ($attr.scrollDown == 'true' && event != 'mouseenter') {
							setTimeout(function () {
								$elem[0].scrollTop = $elem.prop("scrollHeight");
							}, 100);
						}

						perfect.update();
					});
				}

				// This is necessary when you don't watch anything with the scrollbar
				$elem.on('mouseenter', function () {
					update('mouseenter');
				});

				// Possible future improvement: check the type here and use the appropriate watch for non-arrays
				if ($attr.refreshOnChange) {
					$scope.$watchCollection($attr.refreshOnChange, function () {
						update();
					});
				}

				// Rebuild on window resize
				if ($attr.refreshOnResize) {
					jqWindow.on('resize', function () {
						update();
					});
				}

				if ($attr.updateOn) {
					$attr.updateOn.split(' ').forEach(function (eventName) {
						$scope.$on(eventName, update);
					});
				}

				// Unbind resize event and destroy instance
				$elem.on('$destroy', function () {
					jqWindow.off('resize', function () {
						update();
					});
					if (perfect) {
						perfect.destroy();
						perfect = null;
					}
				});
			}
		};
	}

	setupTemplate.$inject = ['$templateCache'];
	function setupTemplate($templateCache) {
		var fsTabset = '<uib-tabset active="options.active" type="options.type" vertical="options.vertical" justified="options.justified"><div id="transclude"></div></uib-tabset>\n';
		var fsTab = '<uib-tab index="options.index" heading="options.heading" disable="options.disable" classes="options.classes" select="options.callback.select" deselect="options.callback.deselect"><div ng-if="options.show" id="transclude"></div></uib-tab>\n';
		var fsTable = '<div ui-grid="TABLE_CONFIG" ui-grid-customer-pagination ui-grid-operation ui-grid-eol-operation ui-grid-selection ui-grid-resize-columns ui-grid-move-columns ui-grid-customer-auto-resize ui-grid-grouping ui-grid-tree-view></div>\n';
		var fsList = ''
			+ '<div class="fsList form-group" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n'
			+ '   <label class="control-label" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span></label>\n'
			+ '   <input type="hidden" ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules">'
			+ '   <input type="hidden" ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" name="{{::fsGridConfig.model}}Selected" ng-model="MODEL_NAME_SELECTED" fs-validator="fsGridConfig.rules">'
			+ '   <div fs-table fs-config="fsGridConfig.gridOptions" fs-verify-model="fsGridConfig.gridOptions.verifyModel" fs-validator-config="fsGridConfig.gridOptions.validatorConfig"/>\n'
			+ '</div>';
		var fsInnerList = ''
			+ '<div class="fsInnerList form-group" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n'
			+ '   <label class="control-label" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span></label>\n'
			+ '   <input type="hidden" ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules">'
			+ '   <input type="hidden" ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" name="{{::fsGridConfig.model}}Selected" ng-model="MODEL_NAME_SELECTED" fs-validator="fsGridConfig.rules">'
			+ '   <div fs-table fs-config="fsGridConfig.gridOptions" fs-verify-model="fsGridConfig.gridOptions.verifyModel" fs-validator-config="fsGridConfig.gridOptions.validatorConfig"/>\n'
			+ '</div>';
		var fsGridLayout = ''
			+ '<div class="panel-group {{::fsGridLayoutController.fsGridCalss}}">\n'
			+ '    <div fs-group ng-repeat="groupContainer in fsGridLayoutController.formContainer" ng-class="{\'collapsed\': groupContainer.collapsed()}" '
			+ '      class="{{::groupContainer.type == \'search\' ? \'search-group\': \'\'}}" ng-model="fsGridLayoutController.ngModel" '
			+ '      fs-verify-model="fsGridLayoutController.fsVerifyModel" '
			+ '      fs-group-config="groupContainer" '
			+ '      fs-validator-config="fsGridLayoutController.fsValidatorConfig">'
			+ '    </div>\n'
			+ '</div>\n';
		var fsAnonymousGroup = ''
			+ '<div IS_ACTIVE_SHOW_EXPRESSION uib-collapse="fsGroupController.groupContainer.collapsed()">\n'
			+ '    <div class="row" ng-repeat="rowObj in fsGroupController.groupContainer.rows">'
			+ '        <div fs-grid ng-repeat="gridObject in rowObj" class="fsGrid {{::gridObject.gridClass}}" '
			+ '           ng-model="fsGroupController.ngModel" '
			+ '           fs-verify-model="fsGroupController.verifyModel" '
			+ '           fs-grid-config="gridObject" '
			+ '           fs-validator-config="fsGroupController.fsValidatorConfig">\n'
			+ '        </div>\n'
			+ '    </div>\n'
			+ '    <div fs-group ng-repeat="subGroupContainer in fsGroupController.groupContainer.gridGroups" ng-class="{\'collapsed\': subGroupContainer.collapsed()}"'
			+ '      class="fsSubGroup {{::groupContainer.type == \'search\' ? \'search-group\': \'\'}}" ng-model="fsGroupController.ngModel"'
			+ '      fs-verify-model="fsGroupController.verifyModel" '
			+ '      fs-group-config="subGroupContainer" '
			+ '      fs-validator-config="fsGroupController.fsValidatorConfig">'
			+ '    </div>\n'
			+ '</div>\n';
		var fsSearchGroup = ''
			+ '<div IS_ACTIVE_SHOW_EXPRESSION class="panel panel-default" uib-collapse="fsGroupController.groupContainer.collapsed()">\n'
			+ '    <div class="panel-body">\n'
			+ '        <div class="row" ng-repeat="rowObj in fsGroupController.groupContainer.rows">'
			+ '            <div fs-grid ng-repeat="gridObject in rowObj" class="fsGrid {{::gridObject.gridClass}}" '
			+ '             ng-model="fsGroupController.ngModel" '
			+ '             fs-verify-model="fsGroupController.verifyModel" '
			+ '             fs-grid-config="gridObject" '
			+ '             fs-validator-config="fsGroupController.fsValidatorConfig">'
			+ '            </div>\n'
			+ '        </div>\n'
			+ '        <div fs-group ng-repeat="subGroupContainer in fsGroupController.groupContainer.gridGroups" ng-class="{\'collapsed\': subGroupContainer.collapsed()}"'
			+ '          class="fsSubGroup {{::groupContainer.type == \'search\' ? \'search-group\': \'\'}}" ng-model="fsGroupController.ngModel"'
			+ '          fs-verify-model="fsGroupController.verifyModel" '
			+ '          fs-group-config="subGroupContainer" '
			+ '          fs-validator-config="fsGroupController.fsValidatorConfig">\n'
			+ '        </div>\n'
			//TODO:should append a search btn in 'fsGroupController.groupContainer.rows'
			+ '        <div class="row">\n'
			+ '            <div class="col-sm-12">\n'
			+ '                <button class="btn btn-primary btn-search pull-right" type="button" '
			+ '                  uib-tooltip="{{fsGroupController.groupContainer.appScope.searchCallbackHotKey.toUpperCase()}}" tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="top-right"'
			+ '                  ng-click="fsGroupController.groupContainer.appScope.searchCallback()">{{"button.text.ok"|translate}}</button>\n'
			+ '            </div>\n'
			+ '       </div>\n'
			+ '   </div>\n'
			+ '</div>\n';
		var fsNormalGroup = ''
			+ '<div class="panel panel-default" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '    <div class="panel-heading {{::fsGroupController.groupContainer.groupClass}}" ng-class="{\'body-collapsed\': fsGroupController.groupContainer.collapsed()}" '
			+ '       style="\'cursor\':{{::(!fsGroupController.groupContainer.disableCollapsed ? \'pointer\' : \'\')}}" CLICK_CALLBACK_EXPRESSION>\n'
			+ '        <span>{{fsGroupController.groupContainer.title|translate}}</span> COLLAPSE_ICON_EXPRESSION\n'
			+ '    </div>\n'
			+ '    <div class="panel-body">\n'
			+ '        <div class="row" ng-repeat="rowObj in fsGroupController.groupContainer.rows">'
			+ '           <div fs-grid ng-repeat="gridObject in rowObj" class="fsGrid {{gridObject.gridClass}}" ng-model="fsGroupController.ngModel" fs-verify-model="fsGroupController.verifyModel" fs-grid-config="gridObject" fs-validator-config="fsGroupController.fsValidatorConfig"></div>\n'
			+ '        </div>\n'
			+ '        <div fs-group ng-repeat="subGroupContainer in fsGroupController.groupContainer.gridGroups" ng-class="{\'collapsed\': subGroupContainer.collapsed()}"'
			+ '          class="fsSubGroup {{::groupContainer.type == \'search\' ? \'search-group\': \'\'}}" ng-model="fsGroupController.ngModel"'
			+ '          fs-verify-model="fsGroupController.verifyModel" '
			+ '          fs-group-config="subGroupContainer" '
			+ '          fs-validator-config="fsGroupController.fsValidatorConfig">\n'
			+ '        </div>\n'
			+ '   </div>\n'
			+ '</div>\n';
		var fsGrid = ''
			+ '<div ng-switch="fsGridConfig.type" ng-class="{\'has-warning\': fsGridController.isChanged() , \'has-error\': fsGridController.showErrors()}">'
			+ '    <div ng-switch-when="color" fs-color ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"></div>\n'
			+ '    <div ng-switch-when="label" fs-label ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"></div>\n'
			+ '    <div ng-switch-when="link" fs-link ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"></div>\n'
			+ '    <div ng-switch-when="image" fs-image ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"></div>\n'
			+ '    <div ng-switch-when="text" fs-text ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="password" fs-password ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="date-range" fs-date-range ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="datetime-range" fs-date-range ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="date" fs-date ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="datetime" fs-date ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="time" fs-time ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="calendarDays" fs-calendar-day ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="checkbox" fs-checkbox ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="checkbox-group" fs-checkbox-group ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="radio" fs-radio ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="radio-card" fs-radio-card ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="textarea" fs-textarea ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="sensitive" fs-sensitive ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="typeahead" fs-typeahead ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="select" fs-select ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			//+ '    <div ng-switch-when="select" fs-customer-select ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="customer-select" fs-customer-select ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="multiple-select" fs-multiple-select ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="number-range" fs-number-range ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="number" fs-number ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="percentage-range" fs-percentage-range ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="percentage" fs-percentage ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="money-range" fs-money-range ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="money" fs-money ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="file-upload" fs-file-upload ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="card" class="card" ng-model="ngModel" fs-card fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="list-group" class="fsListGroup" fs-list-group ng-model="ngModel"  fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="lookup-text" fs-lookup-text ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="lookup-image" fs-lookup-image ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="lookup-multiple" fs-lookup-multiple ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>\n'
			+ '    <div ng-switch-when="button" fs-button fs-button-config="fsGridConfig" fs-ui-grid="grid" fs-ui-grid-row="row" fs-ui-grid-col="col" row="row"></div>\n'
			+ '    <div ng-switch-when="submit" fs-button fs-button-config="fsGridConfig" fs-ui-grid="grid" fs-ui-grid-row="row" fs-ui-grid-col="col" row="row"></div>\n'
			+ '    <div ng-switch-when="buttons" fs-buttons fs-buttons-config="fsGridConfig.buttonsConfig"></div>\n'
			+ '    <div ng-switch-when="innerTable" fs-inner-list ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"></div>\n'
			+ '    <div ng-switch-when="table" fs-list ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"></div>\n'
			+ '</div>\n';
		var fsColor = ''
			+ '<div class="fsColor form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '    LABEL_EXPRESSION\n'
			+ '    <div ng-switch="formComponentCtrl.showStyle(fsGridConfig)">'
			+ '       <div ng-switch-when="edit" class="edit {{::fsGridConfig.prefix ? \'input-group\': \'\'}}">\n'
			+ '           PREFIX_EXPRESSION'
			+ '           <input type="text" colorpicker colorpicker-size="150" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" '
			+ '             uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '             tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}"'
			+ '             fs-uppercase="fsGridConfig.uppercase" fs-validator="fsGridConfig.rules" placeholder="{{fsGridConfig.placeholder|translate}}" fs-grid-prefix="fsGridConfig.prefix">\n'
			+ '       </div>\n'
			+ '       <div ng-switch-when="readonly" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '         tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '           <input disabled type="text" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-grid-filter="fsGridConfig.filter">'
			+ '       </div>\n'
			+ '       <div ng-switch-when="view" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '         tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '           <span class="form-control {{::fsGridConfig.inputClass}}">{{MODEL_NAME MODEL_FILTER}}</span>'
			+ '       </div>\n'
			+ '    </div>\n'
			+ '</div>';
		var fsText = ''
			+ '<div uib-dropdown dropdown-append-to-body="appendToBody()" class="fsText form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '    LABEL_EXPRESSION\n'
			+ '    <div ng-switch="formComponentCtrl.showStyle(fsGridConfig)">'
			+ '       <div ng-switch-when="edit" class="edit {{::(fsGridConfig.prefix || fsGridConfig.postPositionRequired || formComponentCtrl.isPatternActive()) ? \'input-group\': \'\'}}">\n'
			+ '         PREFIX_EXPRESSION\n'
			+ '          <input type="text" id="textInput" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" ng-trim="false"'
			+ '            uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}" tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}"'
			+ '            fs-uppercase="fsGridConfig.uppercase" fs-validator="fsGridConfig.rules" placeholder="{{fsGridConfig.placeholder|translate}}" fs-grid-prefix="fsGridConfig.prefix">\n'
			+ '          PATTERN_EXPRESSION\n'
			+ '          REQUIRED_EXPRESSION\n'
			+ '          PATTERN_CONTENT_EXPRESSION\n'
			+ '       </div>\n'
			+ '       <div ng-switch-when="readonly" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '         tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true"  tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '           <input disabled type="text" class="form-control input-style {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules" fs-grid-filter="fsGridConfig.filter">\n'
			+ '       </div>\n'
			+ '       <div ng-switch-when="view" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '         tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '           <span class="form-control input-style {{::fsGridConfig.inputClass}}">{{MODEL_NAME MODEL_FILTER}}</span>\n'
			+ '       </div>\n'
			+ '    </div>\n';
		+ '</div>';
		var fsLabel = ''
			+ '<div class="fsLabel form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '    <div class="control-label" ng-class="fsGridConfig.labelClass" ng-bind-html="fsGridConfig.label | translate | trustedHtml"></div>\n'
			+ '</div>';
		var fsLink = ''
			+ '<div class="fsLink form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '   LABEL_EXPRESSION\n'
			+ '   <div uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true"  tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '       <a class="{{::fsGridConfig.linkClass}}" ng-click="linkClick()" style="cursor:pointer;">MODEL_NAME</a>\n'
			+ '   </div>\n'
			+ '</div>';
		var fsImage = ''
			+ '<div class="fsImage form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '   LABEL_EXPRESSION\n'
			+ '   <img ng-class="fsGridConfig.imgClass" ng-src="{{::MODEL_NAME | trustedUrl}}"></img>\n'
			+ '</div>';
		var fsPassword = ''
			+ '<div class="fsPassword form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '   LABEL_EXPRESSION'
			+ '   <div ng-switch="formComponentCtrl.showStyle(fsGridConfig)">'
			+ '      <div ng-switch-when="edit" class="edit {{:: ((fsGridConfig.options && (fsGridConfig.options.showSwitcher || fsGridConfig.options.showKeyboard)) || fsGridConfig.postPositionRequired) ? \'input-group\': \'\'}}">\n'
			+ '         <input type="{{isVisible == \'visible\' ? \'text\' : \'password\'}}" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules" placeholder="{{fsGridConfig.placeholder|translate}}"'
			+ '           uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '           tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '		     SWITCH_EXPRESSION\n'
			+ '		     KEYBOARD_EXPRESSION\n'
			+ '          REQUIRED_EXPRESSION\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="readonly" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '           tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '         <input disabled type="password" fs-validator="fsGridConfig.rules" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" placeholder="{{fsGridConfig.placeholder|translate}}">\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="view" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '           tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '         <span class="form-control input-style {{::fsGridConfig.inputClass}}">{{MODEL_NAME MODEL_FILTER}}</span>\n'
			+ '      </div>\n'
			+ '   </div>\n'
			+ '</div>\n';
		var fsRadio = ''
			+ '<div class="fsRadio form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '   LABEL_EXPRESSION\n'
			+ '   <div ng-switch="formComponentCtrl.showStyle(fsGridConfig)">'
			+ '      <div ng-switch-when="edit" class="edit {{::fsGridConfig.postPositionRequired ? \'input-group\': \'\'}}">\n'
			+ '         <input type="hidden" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules">'
			+ '         <ul class="list-inline input-group {{::fsGridConfig.inputClass}}"'
			+ '              uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '              tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '             <li ng-repeat="item in fsGridConfig.options.data"><input type="radio" id="{{::fsGridConfig.model+$index}}" ng-model="MODEL_NAME" ng-value="item.value" ng-checked="item.value==MODEL_NAME"><label for="{{::fsGridConfig.model+$index}}" >{{item.label | translate}}</label></li>\n'
			+ '         </ul>\n'
			+ '         REQUIRED_EXPRESSION\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="readonly">\n'
			+ '         <input type="hidden" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules">'
			+ '         <ul class="list-inline input-group {{::fsGridConfig.inputClass}}"'
			+ '              uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '              tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '             <li ng-repeat="item in fsGridConfig.options.data"><input disabled type="radio" ng-model="MODEL_NAME" ng-value="item.value" ng-checked="item.value==MODEL_NAME"><label>{{item.label | translate}}</label></li>\n'
			+ '         </ul>\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="view">\n'
			+ '         <input type="hidden" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules">'
			+ '         <ul class="list-inline input-group {{::fsGridConfig.inputClass}}"'
			+ '              uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '              tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '             <li ng-repeat="item in fsGridConfig.options.data" ng-if="item.value==MODEL_NAME"><label>{{item.label | translate}}</label></li>\n'
			+ '         </ul>\n'
			+ '      </div>\n'
			+ '   </div>\n'
			+ '</div>';
		var fsRadioCard = ''
			+ '<div class="fsRadioCard form-group card-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '   <div class="media">\n'
			+ '     <div class="media-left" ng-click="formComponentCtrl.left()" ng-show="formComponentCtrl.cardOffset > 0" style="cursor:pointer;"><span class="icon icon-arrow-left"></span></div>\n'
			+ '     <div class="media-body" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '         <div class="card" ng-class="{active: (MODEL_NAME == cardConfig.value)}" ng-repeat="cardConfig in fsGridConfig.cardConfigs" ng-model="ngModel" fs-card fs-grid-config="cardConfig" ng-click="MODEL_NAME = cardConfig.value" ng-show="formComponentCtrl.cardOffset <= $index" style="cursor: pointer;"></div>'
			+ '     </div>\n'
			+ '     <div class="media-right" ng-click="formComponentCtrl.right()" ng-show="formComponentCtrl.showCardCount < (fsGridConfig.cardConfigs.length - formComponentCtrl.cardOffset)" style="cursor: pointer;"><span class="icon icon-arrow-right"></span></div>\n'
			+ '   </div>\n'
			+ '</div>';
		var fsCheckbox = ''
			+ '<div class="fsCheckbox form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '   LABEL_EXPRESSION\n'
			+ '   <div ng-switch="formComponentCtrl.showStyle(fsGridConfig)">'
			+ '      <div ng-switch-when="edit" style="overflow:auto;" class="{{::fsGridConfig.postPositionRequired ? \'input-group\': \'\'}}">\n'
			+ '         <input type="hidden" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules">\n'
			+ '         <ul class="list-inline input-group"'
			+ '           uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '           tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '             <li class="{{::fsGridConfig.inputClass}}" ng-repeat="item in fsGridConfig.options.data">'
			+ '             <input type="checkbox" id="{{::fsGridConfig.model+$index}}" value="{{::item.value}}" ng-disabled="item.disabled" ng-checked="indexOfModel(item.value) > -1" ng-click="toggleSelection(item.value);clicked();"><label for="{{::fsGridConfig.model+$index}}">{{item.label | translate}}</label></li>'
			+ '         </ul>\n'
			+ '         REQUIRED_EXPRESSION\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="readonly" style="overflow:auto;">'
			+ '         <ul class="list-inline input-group"'
			+ '           uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '           tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '             <li class="{{::fsGridConfig.inputClass}}" ng-repeat="item in fsGridConfig.options.data">'
			+ '          <input disabled type="checkbox" value="{{::item.value}}" ng-checked="indexOfModel(item.value) > -1"><label>{{item.label | translate}}</label></li>'
			+ '         </ul>'
			+ '      </div>\n'
			+ '      <div ng-switch-when="view" style="overflow:auto;">'
			+ '         <ul class="list-inline input-group"'
			+ '           uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '           tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '             <li class="{{::fsGridConfig.inputClass}}" ng-repeat="item in fsGridConfig.options.data" ng-if="indexOfModel(item.value) > -1"><label>{{item.label | translate}}</label></li>'
			+ '         </ul>'
			+ '      </div>\n'
			+ '   </div>\n'
			+ '</div>';
		var fsCheckboxGroup = ''
			+ '<div class="fsCheckboxGroup fsCheckbox form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '   LABEL_EXPRESSION\n'
			+ '   <div ng-switch="formComponentCtrl.showStyle(fsGridConfig)">'
			+ '      <div ng-switch-when="edit" class="edit panel panel-default" ng-repeat="group in fsGridConfig.options.groups" ng-init="groupIndex = $index">\n'
			+ '          <input type="hidden" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules">'
			+ '          <div ng-class="{\'body-collapsed\': group.isCollapsed}" class="panel-heading">\n'
			+ '              <input type="checkbox" id="{{fsGridConfig.model+\'.group\'+groupIndex}}" ng-if="group.showHeadCheckbox" ng-model="group.allChecked" ng-checked="group.allChecked" ng-click="toggleSelectionAll($event, group)"><label for="{{fsGridConfig.model+\'.group\'+groupIndex}}" class="control-label">{{group.title | translate}}</label>'
			+ '              <i class="pull-right icon" style="cursor: pointer;" ng-class="{\'icon-down\': !group.isCollapsed, \'icon-up\': group.isCollapsed}" ng-click="toggleGroupCollapse(group)"></i>'
			+ '          </div>\n'
			+ '          <div class="panel-body" uib-collapse="group.isCollapsed">\n'
			+ '              <ul class="list-inline">\n'
			+ '                  <li class="{{::fsGridConfig.inputClass}}"  ng-repeat="item in group.data" >'
			+ '                      <input type="checkbox" id="{{::fsGridConfig.model+groupIndex+$index}}" value="{{::item.value}}" ng-checked="indexOfModel(item.value) > -1" ng-click="toggleSelectionAll($event, group, item.value)"><label for="{{::fsGridConfig.model+groupIndex+$index}}" >{{item.label | translate}}</label>'
			+ '                  </li>'
			+ '                  <li>{{item.desc | translate}}</li>'
			+ '              </ul>\n'
			+ '          </div>\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="readonly" class="panel panel-default" ng-repeat="group in fsGridConfig.options.groups">\n'
			+ '          <div class="panel-heading">\n'
			+ '              <label class="control-label">{{group.title | translate}}</label>'
			+ '              <i class="pull-right icon" style="cursor: pointer;" ng-class="{\'icon-down\': !group.isCollapsed, \'icon-up\': group.isCollapsed}" ng-click="toggleGroupCollapse(group)"></i>'
			+ '          </div>\n'
			+ '          <div class="panel-body" uib-collapse="group.isCollapsed">\n'
			+ '              <ul class="list-inline">\n'
			+ '                  <li class="{{::fsGridConfig.inputClass}}" ng-repeat="item in group.data">'
			+ '                      <input disabled type="checkbox" value="{{::item.value}}" ng-checked="indexOfModel(item.value) > -1"><label>{{item.label | translate}}</label>'
			+ '                  </li>'
			+ '              </ul>\n'
			+ '          </div>\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="view" class="panel panel-default" ng-repeat="group in fsGridConfig.options.groups">\n'
			+ '          <div class="panel-heading">\n'
			+ '              <label class="control-label">{{group.title | translate}}</label>'
			+ '              <i class="pull-right icon" style="cursor: pointer;" ng-class="{\'icon-down\': !group.isCollapsed, \'icon-up\': group.isCollapsed}" ng-click="toggleGroupCollapse(group)"></i>'
			+ '          </div>\n'
			+ '          <div class="panel-body" uib-collapse="group.isCollapsed">\n'
			+ '              <ul class="list-inline">\n'
			+ '                  <li ng-repeat="item in group.data" ng-if="indexOfModel(item.value) > -1"><label>{{item.label | translate}}</label></li>'
			+ '              </ul>\n'
			+ '          </div>\n'
			+ '      </div>\n'
			+ '   </div>\n'
			+ '</div>';
		var fsDate = ''
			+ '<div class="fsDate form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '   LABEL_EXPRESSION\n'
			+ '   <div ng-switch="formComponentCtrl.showStyle(fsGridConfig)">'
			+ '      <div ng-switch-when="edit" class="edit input-group">\n'
			+ '          <span class="input-group-addon range-label" ng-class="{\'focus\':focused}" ng-if="MODEL_NAME" ng-click="MODEL_NAME=null;setDirty()" style="font-family: Arial, sans-serif;"><div>&minus;</div></span>'
			+ '          <input type="text" class="form-control {{::fsGridConfig.inputClass}}"'
			+ '            uib-datepicker-popup="{{fsGridConfig.format}}"'
			+ '            datepicker-options="_dateOptions" '
			+ '            datepicker-append-to-body="true"'
			+ '            close-on-date-selection="fsGridConfig.closeOnDateSelection"'
			+ '            alt-input-formats="formComponentCtrl.altInputFormats"'
			+ '            name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules" is-open="_opened"'
			+ '            ng-focus="focused = true" ng-blur="focused = false" placeholder="{{fsGridConfig.placeholder|translate}}"'
			+ '            uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '            tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}"/>\n'
			+ '          <span class="input-group-btn">\n'
			+ '              <button type="button" class="btn btn-default {{::fsGridConfig.inputClass}}" ng-class="{\'focus\':focused}" ng-click="_dateOptions.datepickerMode = fsGridConfig.options.mode || \'day\';_opened=!_opened">\n'
			+ '                  <i class="icon icon-calendar"></i>\n'
			+ '              </button>\n'
			+ '          </span>\n'
			+ '  	     REQUIRED_EXPRESSION\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="readonly" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '          <input disabled type="text" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules" uib-datepicker-popup="{{fsGridConfig.format}}" datepicker-options="_dateOptions" is-open="_opened">'
			+ '      </div>\n'
			+ '      <div ng-switch-when="view" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '          <span class="form-control input-style {{::fsGridConfig.inputClass}}">{{MODEL_NAME MODEL_FILTER}}</span>\n'
			+ '      </div>\n'
			+ '   </div>\n'
			+ '</div>\n';
		var fsDateRange = ''
			+ '<div class="fsDateRange form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '   LABEL_EXPRESSION\n'
			+ '   <div ng-switch="formComponentCtrl.showStyle(fsGridConfig)">'
			+ '      <div ng-switch-when="edit" class="edit input-group">\n'
			+ '          <span class="input-group-addon range-label" ng-class="{\'focus\':focused}">{{\'label.global.from\' | translate}}<div ng-if="MODEL_NAME.from" ng-click="MODEL_NAME.from = null;setDirty()" style="font-family: Arial, sans-serif;">&minus;</div></span>'
			+ '          <input type="text" class="form-control 1st {{::fsGridConfig.inputClass}}" ng-class="{\'focus\':focused}"'
			+ '            uib-datepicker-popup="{{fsGridConfig.format}}"'
			+ '            datepicker-append-to-body="true"'
			+ '            datepicker-options="_fromDateOptions"'
			+ '            close-on-date-selection="false"'
			+ '            is-from="true"'
			+ '            alt-input-formats="formComponentCtrl.altInputFormats"'
			+ '            name="{{::fsGridConfig.model}}.from" ng-model="MODEL_NAME.from" fs-validator="fsGridConfig.rules" is-open="_opened"'
			+ '            ng-focus="focused = true" ng-blur="focused = false" placeholder="{{fsGridConfig.placeholder|translate}}"'
			+ '            uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '            tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}"/>\n'
			+ '          <span class="input-group-addon range-label" ng-class="{\'focus\':focused}" style="border-left: 0;border-right: 0;">{{\'label.global.to\' | translate}}<div ng-if="MODEL_NAME.to" ng-click="MODEL_NAME.to = null;setDirty()" style="font-family: Arial, sans-serif;">&minus;</div></span>'
			+ '          <input type="text" class="form-control 2nd {{::fsGridConfig.inputClass}}" ng-class="{\'focus\':focused}"'
			+ '            uib-datepicker-popup="{{fsGridConfig.format}}"'
			+ '            datepicker-options="_toDateOptions"'
			+ '            datepicker-append-to-body="true"'
			+ '            close-on-date-selection="fsGridConfig.closeOnDateSelection"'
			+ '            is-to="true"'
			+ '            alt-input-formats="formComponentCtrl.altInputFormats"'
			+ '            name="{{::fsGridConfig.model}}.to" ng-model="MODEL_NAME.to" fs-validator="fsGridConfig.rules" is-open="_opened"'
			+ '            ng-focus="focused = true" ng-blur="focused = false" placeholder="{{fsGridConfig.placeholder|translate}}"'
			+ '            uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '            tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}"/>\n'
			+ '          <span class="input-group-btn">\n'
			+ '              <button type="button" class="btn btn-default {{::fsGridConfig.inputClass}}" ng-class="{\'focus\':focused}" ng-click="_fromDateOptions.datepickerMode = fsGridConfig.options.mode || \'day\';_opened=!_opened">\n'
			+ '                  <i class="icon icon-calendar"></i>\n'
			+ '              </button>\n'
			+ '          </span>\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="readonly" class="input-group" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '          <span class="input-group-addon range-label readonly" ng-class="{\'focus\':focused}">{{\'label.global.from\' | translate}}</span>'
			+ '          <input type="text" disabled class="form-control 1st {{::fsGridConfig.inputClass}}" ng-class="{\'focus\':focused}" uib-datepicker-popup="{{fsGridConfig.format}}" datepicker-append-to-body="true" close-on-date-selection="false" is-from="true" alt-input-formats="formComponentCtrl.altInputFormats" name="{{::fsGridConfig.model}}.from" ng-model="MODEL_NAME.from" fs-validator="fsGridConfig.rules" is-open="_opened"'
			+ '            ng-focus="focused = true" ng-blur="focused = false" datepicker-options="_fromDateOptions" placeholder="{{fsGridConfig.placeholder|translate}}"'
			+ '            uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '            tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}"/>\n'
			+ '          <span class="input-group-addon range-label readonly" ng-class="{\'focus\':focused}" style="border-left: 0;border-right: 0;">{{\'label.global.to\' | translate}}</span>'
			+ '          <input type="text" disabled class="form-control 2nd {{::fsGridConfig.inputClass}}" ng-class="{\'focus\':focused}" uib-datepicker-popup="{{fsGridConfig.format}}" datepicker-append-to-body="true" close-on-date-selection="fsGridConfig.closeOnDateSelection" is-to="true" alt-input-formats="formComponentCtrl.altInputFormats" name="{{::fsGridConfig.model}}.to" ng-model="MODEL_NAME.to" fs-validator="fsGridConfig.rules" is-open="_opened"'
			+ '            ng-focus="focused = true" ng-blur="focused = false" datepicker-options="_toDateOptions" placeholder="{{fsGridConfig.placeholder|translate}}"'
			+ '            uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '            tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}"/>\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="view" class="form-inline" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}">\n'
			+ '         <div class="form-group">\n'
			+ '             <label class="control-label">{{\'label.global.from\' | translate}}</label>'
			+ '             <div class="form-control input-style {{fsGridConfig.inputClass}}">{{MODEL_NAME.from MODEL_FILTER}}</div>'
			+ '         </div>\n'
			+ '         <div class="form-group">\n'
			+ '             <label class="control-label">{{\'label.global.to\' | translate}}</label>'
			+ '             <div class="form-control input-style {{fsGridConfig.inputClass}}">{{MODEL_NAME.to MODEL_FILTER}}</div>'
			+ '         </div>\n'
			+ '      </div>\n'
			+ '   </div>\n'
			+ '</div>\n';
		var fsTime = ''
			+ '<div class="fsTime form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '   LABEL_EXPRESSION\n'
			+ '   <div ng-switch="formComponentCtrl.showStyle(fsGridConfig)">'
			+ '      <div ng-switch-when="edit" class="edit {{::fsGridConfig.postPositionRequired ? \'input-group\': \'\'}}" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">'
			+ '          <input type="hidden" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules">\n'
			+ '          <div uib-timepicker ng-model="MODEL_NAME" show-seconds="fsGridConfig.options.showSeconds" show-spinners="_showSpinners" hour-step="fsGridConfig.options.hourStep" minute-step="fsGridConfig.options.minuteStep" show-meridian="fsGridConfig.options.isMeridian"></div>\n'
			+ '  	     REQUIRED_EXPRESSION\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="readonly" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">'
			+ '          <input type="hidden" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules">\n'
			+ '          <div uib-timepicker ng-model="MODEL_NAME" show-seconds="fsGridConfig.options.showSeconds" show-spinners="_showSpinners" hour-step="fsGridConfig.options.hourStep" minute-step="fsGridConfig.options.minuteStep" show-meridian="fsGridConfig.options.isMeridian" readonly-input="true" ng-disabled="true" ></div>\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="view" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">'
			+ '          <span class="form-control input-style {{::fsGridConfig.inputClass}}">{{MODEL_NAME | dateFormat:\'HH:mm:ss\'}}</span>\n'
			+ '      </div>\n'
			+ '   </div>\n'
			+ '</div>\n';
		var fsCalendarDay = ''
			+ '<div class="fsCalendarDay form-group" ng-repeat="option in _calendarOptions">\n'
			+ '   <input type="hidden" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules">\n'
			+ '   <div uib-datepicker template-url="fs/layout/template/calendar/calendarDay.html" ng-model="option._tempDate" class="well well-sm" datepicker-options="option"></div>'
			+ '</div>\n';
		var fsTextarea = ''
			+ '<div class="fsTextarea form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '   LABEL_EXPRESSION\n'
			+ '   <div ng-switch="formComponentCtrl.showStyle(fsGridConfig)">'
			+ '      <div ng-switch-when="edit" class="edit {{::fsGridConfig.postPositionRequired ? \'input-group\': \'\'}}">\n'
			+ '     	  <textarea class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-uppercase="fsGridConfig.uppercase" fs-validator="fsGridConfig.rules" placeholder="{{fsGridConfig.placeholder|translate}}" rows= "{{::fsGridConfig.options.rows}}" cols= "{{::fsGridConfig.options.cols}}"'
			+ '             uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '             tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '	     	  </textarea>\n'
			+ '           REQUIRED_EXPRESSION\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="readonly" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '         tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '          <textarea disabled class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" fs-validator="fsGridConfig.rules" ng-model="MODEL_NAME" rows= "{{::fsGridConfig.options.rows}}" cols= "{{::fsGridConfig.options.cols}}" fs-grid-filter="fsGridConfig.filter"></textarea>\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="view" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '         tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '          <pre class="form-control input-style {{::fsGridConfig.inputClass}}">{{MODEL_NAME MODEL_FILTER}}</pre>\n'
			+ '      </div>\n'
			+ '   </div>\n'
			+ '</div>\n';
		var fsSelect = ''
			+ '<div class="fsSelect form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '   LABEL_EXPRESSION\n'
			+ '   <div ng-switch="formComponentCtrl.showStyle(fsGridConfig)">'
			+ '      <div ng-switch-when="edit" class="edit {{::fsGridConfig.postPositionRequired ? \'input-group\': \'\'}}">\n'
			+ '      	 <select class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" ng-options="option.value as option.label|translate for option in options"'
			+ '             uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '             tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}" fs-validator="fsGridConfig.rules">\n'
			+ '          <option value="" ng-if="fsGridConfig.options.hasEmptyOption"></option>\n'
			+ '      	 </select>\n'
			+ '          REQUIRED_EXPRESSION\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="readonly" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '          <select fs-validator="fsGridConfig.rules" disabled class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" ng-options="option.value as option.label|translate for option in options">\n'
			+ '              <option value="" ng-if="fsGridConfig.options.hasEmptyOption"></option>\n'
			+ '          </select>\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="view" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '          <ul class="list-inline">\n'
			+ '             <li class="{{::fsGridConfig.inputClass}}" ng-repeat="option in options" ng-if="option.value == MODEL_NAME"><span>{{option.label | translate}}</span></li>'
			+ '          </ul>\n'
			+ '      </div>\n'
			+ '   </div>\n'
			+ '</div>\n';

		var fsCustomerSelect = ''
			+ '<div class="fsCustomerSelect form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '   LABEL_EXPRESSION\n'
			+ '   <div ng-switch="formComponentCtrl.showStyle(fsGridConfig)" uib-dropdown dropdown-append-to-body="appendToBody()" is-open="isOpen" auto-close="{{fsGridConfig.autoClose}}">'
			+ '      <div ng-switch-when="edit" class="edit input-group edit" uib-dropdown-toggle>\n'
			+ '          <input type="text" value="{{formComponentCtrl.VIEW_MODEL_NAMEViewModel.label | translate}}" disabled class="form-control {{::fsGridConfig.inputClass}}" name="{{::MODEL_NAMEViewValue}}"'
			+ '            ng-focus="focused = true" ng-blur="focused = false" placeholder="{{fsGridConfig.placeholder|translate}}"'
			+ '            uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '            tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}"/>\n'
			+ '          <input type="hidden" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules">\n'
			+ '          <span class="input-group-btn">\n'
			+ '              <button type="button" class="readonly btn btn-default {{::fsGridConfig.inputClass}}" ng-class="{\'focus\':focused}">\n'
			+ '                  <i class="icon icon-down"></i>\n'
			+ '              </button>\n'
			+ '          </span>\n'
			+ '  	     REQUIRED_EXPRESSION\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="readonly" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '          <input type="text" value="{{formComponentCtrl.VIEW_MODEL_NAMEViewModel.label | translate}}" disabled class="form-control {{::fsGridConfig.inputClass}}" name="{{::MODEL_NAMEViewValue}}"/>\n'
			+ '          <input type="hidden" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules">\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="view" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '          <span class="form-control input-style {{::fsGridConfig.inputClass}}">{{formComponentCtrl.VIEW_MODEL_NAMEViewModel.label | translate}}</span>'
			+ '          <input type="hidden" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules">\n'
			+ '      </div>\n'
			+ '      CUSTOMER_OPTIONS_EXPRESSION\n'
			+ '   </div>\n'
			+ '</div>\n';

		var fsCustomerSelectItem = ''
			+ '<li id="item{{::fsItemConfig.optionLevel}}{{::$index}}" ng-repeat="itemData in fsItemConfig.data" ng-class="{\'dropdown-submenu\': itemData.subSelectOption, \'open\': formComponentCtrl.isOpen(fsItemConfig, itemData)}">\n'
			+ '    <a ng-class="{\'selected\':itemData.value == customerSelect[fsGridConfig.model + \'ViewModel\'].value, '
			+ '                  \'focus\':(formComponentCtrl.isOpen(fsItemConfig, itemData) || itemData.value == customerSelect[fsGridConfig.model + \'ViewModel\'].value) }" '
			+ '       ng-click="formComponentCtrl.clickItem(fsItemConfig, itemData)" ng-mouseover="formComponentCtrl.mouseover(fsItemConfig, itemData)" style="white-space: normal;" href="#" >'
			+ '    <div class="media">\n'
			+ '        <div class="media-body media-middle">\n'
			+ '            <span>{{itemData.label | translate}}</span>'
			+ '        </div>\n'
			+ '        <div class="media-right media-middle" ng-if="itemData.subSelectOption">\n'
			+ '            <span class="icon icon-arrow-right_1"></span>\n'
			+ '        </div>\n'
			+ '    </div></a>\n'
			+ '    <ul ng-if="itemData.subSelectOption" class="animated fadeInUp dropdown-menu" fs-customer-select-item ng-model="ngModel" fs-grid-config="fsGridConfig" fs-item-config="itemData.subSelectOption" style="min-width:200px"></ul>'
			+ '</li>\n';
		//TODO: change view style
		var fsTypeahead = ''
			+ '<div class="fsTypeahead form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '   LABEL_EXPRESSION\n'
			+ '   <div ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" class="{{::fsGridConfig.postPositionRequired ? \'input-group\': \'\'}}">'
			+ '       <input type="hidden" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules">\n'
			+ '       <input type="text" class="form-control {{::fsGridConfig.inputClass}}" ng-model="_typeaheadModel.label" fs-uppercase="fsGridConfig.uppercase" placeholder="{{fsGridConfig.placeholder|translate}}" '
			+ '         uib-typeahead="item.value as item.label|translate for item in fsGridConfig.options.data | filter:{display:$viewValue} | limitTo:fsGridConfig.options.length" '
			+ '         typeahead-editable="true" ng-change="changeGridModelValue()" typeahead-on-select="typeaheadOnSelect($item, $model, $label, $event)"'
			+ '         typeahead-append-to-body="true"'
			+ '         typeahead-template-url="fs/layout/template/fsTypeaheadOption.html"'
			+ '         typeahead-show-hint="false" typeahead-min-length="0" '
			+ '         uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '         tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">'
			+ '        REQUIRED_EXPRESSION\n'
			+ '	   </div>\n'
			+ '   <div ng-if="formComponentCtrl.isReadonly(fsGridConfig)" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '     tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '       <input disabled type="text" class="form-control {{::fsGridConfig.inputClass}}" ng-model="_typeaheadModel.label" placeholder="{{fsGridConfig.placeholder|translate}}" fs-grid-filter="fsGridConfig.filter">'
			+ '   <div>\n'
			+ '</div>\n';
		var fsTypeaheadOption = '<a><span ng-bind-html="match.label | translate | uibTypeaheadHighlight:query"></span></a>\n';
		var fsSensitive = ''
			+ '<div class="fsSensitive form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '   LABEL_EXPRESSION\n'
			+ '   <div ng-switch="formComponentCtrl.showStyle(fsGridConfig)">'
			+ '      <div ng-switch-when="edit" class="edit {{::(fsGridConfig.prefix || fsGridConfig.postPositionRequired) ? \'input-group\':\'\'}}">\n'
			+ '          PREFIX_EXPRESSION'
			+ '          <input type="text" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" '
			+ '            uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '            tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}"'
			+ '            fs-uppercase="fsGridConfig.uppercase" fs-validator="fsGridConfig.rules" fs-sst-format="fsGridConfig.options" placeholder="{{fsGridConfig.placeholder|translate}}" fs-grid-prefix="fsGridConfig.prefix">\n'
			+ '          REQUIRED_EXPRESSION\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="readonly" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '          <input disabled fs-validator="fsGridConfig.rules" type="text" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-sst-format="{{fsGridConfig.options}}">'
			+ '      </div>\n'
			+ '      <div ng-switch-when="view" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '          <span class="form-control input-style {{fsGridConfig.inputClass}}">{{getDisplay()}}</span>'
			+ '      </div>\n'
			+ '   </div>\n'
			+ '</div>\n';
		var fsMoney = ''
			+ '<div class="fsMoney form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '   LABEL_EXPRESSION\n'
			+ '   <div ng-switch="formComponentCtrl.showStyle(fsGridConfig)">'
			+ '      <div ng-switch-when="edit" class="edit {{::fsGridConfig.postPositionRequired ? \'input-group\':\'\'}}">'
			+ '          <input type="text" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules" fs-money-format="{{fsGridConfig.format}}" fs-grid-filter="fsGridConfig.filter" placeholder="{{fsGridConfig.placeholder|translate}}"'
			+ '            ng-paste="handlePaste($event)" style="text-align: right;"'
			+ '            uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '            tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '          REQUIRED_EXPRESSION\n'
			+ '      </div>'
			+ '      <div ng-switch-when="readonly" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '          <input style="text-align: right;" disabled type="text" fs-validator="fsGridConfig.rules" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-money-format="{{fsGridConfig.format}}" fs-grid-filter="fsGridConfig.filter">'
			+ '      </div>\n'
			+ '      <div ng-switch-when="view" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '          <input disabled type="hidden" fs-validator="fsGridConfig.rules" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME">'
			+ '          <div class="form-control input-style {{::fsGridConfig.inputClass}}" ng-bind-html="moneyDisplay | trustedHtml"></div>'
			+ '      </div>\n'
			+ '   </div>\n'
			+ '</div>\n';
		var fsMoneyRange = ''
			+ '<div class="fsMoneyRange form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '   LABEL_EXPRESSION\n'
			+ '   <div ng-switch="formComponentCtrl.showStyle(fsGridConfig)">'
			+ '      <div ng-switch-when="edit" class="edit input-group">\n'
			+ '          <span class="input-group-addon range-label" ng-class="{\'focus\':focused}">{{\'label.global.from\' | translate}}</span>'
			+ '          <input type="text" class="form-control 1st {{::fsGridConfig.inputClass}}" ng-class="{\'focus\':focused}" name="{{::fsGridConfig.model}}.from" ng-model="MODEL_NAME.from" fs-validator="fsGridConfig.rules" fs-money-format="{{fsGridConfig.format}}" fs-grid-filter="fsGridConfig.filter" placeholder="{{fsGridConfig.placeholder|translate}}"'
			+ '            ng-paste="handlePaste($event)" style="text-align: right;" ng-focus="focused = true" ng-blur="focused = false" datepicker-options="_dateOptions" placeholder="{{fsGridConfig.placeholder|translate}}"'
			+ '            uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '            tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}"/>\n'
			+ '          <span class="input-group-addon range-label" ng-class="{\'focus\':focused}" style="border-left: 0;border-right: 0;">{{\'label.global.to\' | translate}}</span>'
			+ '          <input type="text" class="form-control 2nd {{::fsGridConfig.inputClass}}" ng-class="{\'focus\':focused}" name="{{::fsGridConfig.model}}.to" ng-model="MODEL_NAME.to" fs-validator="fsGridConfig.rules" fs-money-format="{{fsGridConfig.format}}" fs-grid-filter="fsGridConfig.filter" placeholder="{{fsGridConfig.placeholder|translate}}"'
			+ '            ng-paste="handlePaste($event)" style="text-align: right;" ng-focus="focused = true" ng-blur="focused = false" datepicker-options="_dateOptions" placeholder="{{fsGridConfig.placeholder|translate}}"'
			+ '            uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '            tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}"/>\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="readonly" class="input-group">\n'
			+ '          <span class="input-group-addon range-label readonly">{{\'label.global.from\' | translate}}</span>'
			+ '          <input disabled type="text" style="text-align: right;" class="form-control 1st {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}.from" ng-model="MODEL_NAME.from" fs-money-format="{{fsGridConfig.format}}" fs-grid-filter="fsGridConfig.filter"/>\n'
			+ '          <span class="input-group-addon range-label readonly" style="border-left: 0;border-right: 0;" style="border-left: 0;border-right: 0;">{{\'label.global.to\' | translate}}</span>'
			+ '          <input disabled type="text" style="text-align: right;" class="form-control 2nd {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}.to" ng-model="MODEL_NAME.to" fs-money-format="{{fsGridConfig.format}}" fs-grid-filter="fsGridConfig.filter"/>\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="view" class="form-inline">\n'
			+ '         <div class="form-group">\n'
			+ '             <label class="control-label">{{\'label.global.from\' | translate}}</label>'
			+ '             <div class="form-control input-style {{::fsGridConfig.inputClass}}" ng-bind-html="fromMoneyDisplay | trustedHtml"></div>'
			+ '         </div>\n'
			+ '         <div class="form-group">\n'
			+ '             <label class="control-label">{{\'label.global.to\' | translate}}</label>'
			+ '             <div class="form-control input-style {{::fsGridConfig.inputClass}}" ng-bind-html="toMoneyDisplay | trustedHtml"></div>'
			+ '         </div>\n'
			+ '      </div>\n'
			+ '   </div>\n'
			+ '</div>\n';
		var fsNumber = ''
			+ '<div class="fsNumber form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '   LABEL_EXPRESSION\n'
			+ '   <div ng-switch="formComponentCtrl.showStyle(fsGridConfig)">'
			+ '      <div ng-switch-when="edit" class="edit {{::fsGridConfig.postPositionRequired ? \'input-group\':\'\'}}">'
			+ '          <input type="text" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules" fs-num-format="{{fsGridConfig.format}}" fs-grid-filter="fsGridConfig.filter" placeholder="{{fsGridConfig.placeholder|translate}}"'
			+ '            ng-paste="handlePaste($event)" style="text-align: right;"'
			+ '            uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '            tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '          REQUIRED_EXPRESSION\n'
			+ '      </div>'
			+ '      <div ng-switch-when="readonly" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '           tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '          <input disabled type="text" style="text-align: right;" fs-validator="fsGridConfig.rules" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-num-format="{{fsGridConfig.format}}" fs-grid-filter="fsGridConfig.filter">'
			+ '      </div>\n'
			+ '      <div ng-switch-when="view" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '           tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '          <div class="form-control input-style {{::fsGridConfig.inputClass}}" ng-bind-html="numberDisplay | trustedHtml"></div>'
			+ '      </div>\n'
			+ '   </div>\n'
			+ '</div>\n';
		var fsNumberRange = ''
			+ '<div class="fsNumberRange form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '   LABEL_EXPRESSION\n'
			+ '   <div ng-switch="formComponentCtrl.showStyle(fsGridConfig)">'
			+ '      <div ng-switch-when="edit" class="edit input-group">\n'
			+ '          <span class="input-group-addon range-label" ng-class="{\'focus\':focused}">{{\'label.global.from\' | translate}}</span>'
			+ '          <input type="text" style="text-align: right;" class="form-control 1st {{::fsGridConfig.inputClass}}" ng-class="{\'focus\':focused}" name="{{::fsGridConfig.model}}.from" ng-model="MODEL_NAME.from" fs-validator="fsGridConfig.rules" fs-num-format="{{fsGridConfig.format}}" fs-grid-filter="fsGridConfig.filter" placeholder="{{fsGridConfig.placeholder|translate}}"'
			+ '            ng-paste="handlePaste($event)" ng-focus="focused = true" ng-blur="focused = false" datepicker-options="_dateOptions" placeholder="{{fsGridConfig.placeholder|translate}}"'
			+ '            uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '            tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}"/>\n'
			+ '          <span class="input-group-addon range-label" ng-class="{\'focus\':focused}" style="border-left: 0;border-right: 0;">{{\'label.global.to\' | translate}}</span>'
			+ '          <input type="text" style="text-align: right;" class="form-control 2nd {{::fsGridConfig.inputClass}}" ng-class="{\'focus\':focused}" name="{{::fsGridConfig.model}}.to" ng-model="MODEL_NAME.to" fs-validator="fsGridConfig.rules" fs-num-format="{{fsGridConfig.format}}" fs-grid-filter="fsGridConfig.filter" placeholder="{{fsGridConfig.placeholder|translate}}"'
			+ '            ng-paste="handlePaste($event)" ng-focus="focused = true" ng-blur="focused = false" datepicker-options="_dateOptions" placeholder="{{fsGridConfig.placeholder|translate}}"'
			+ '            uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '            tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}"/>\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="readonly" class="input-group">\n'
			+ '          <span class="input-group-addon range-label readonly">{{\'label.global.from\' | translate}}</span>'
			+ '          <input disabled type="text" style="text-align: right;" class="form-control 1st {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}.from" ng-model="MODEL_NAME.from" fs-num-format="{{fsGridConfig.format}}" fs-grid-filter="fsGridConfig.filter"/>\n'
			+ '          <span class="input-group-addon range-label readonly" style="border-left: 0;border-right: 0;">{{\'label.global.to\' | translate}}</span>'
			+ '          <input disabled type="text" style="text-align: right;" class="form-control 2nd {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}.to" ng-model="MODEL_NAME.to" fs-num-format="{{fsGridConfig.format}}" fs-grid-filter="fsGridConfig.filter"/>\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="view" class="form-inline">\n'
			+ '         <div class="form-group">\n'
			+ '             <label class="control-label">{{\'label.global.from\' | translate}}</label>'
			+ '             <div class="form-control input-style {{::fsGridConfig.inputClass}}" ng-bind-html="fromNumberDisplay | trustedHtml"></div>'
			+ '         </div>\n'
			+ '         <div class="form-group">\n'
			+ '             <label class="control-label">{{\'label.global.to\' | translate}}</label>'
			+ '             <div class="form-control input-style {{::fsGridConfig.inputClass}}" ng-bind-html="toNumberDisplay | trustedHtml"></div>'
			+ '         </div>\n'
			+ '      </div>\n'
			+ '   </div>\n'
			+ '</div>\n';
		var fsPercentage = ''
			+ '<div class="fsPercentage form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '   <div ng-switch="formComponentCtrl.showStyle(fsGridConfig)">'
			+ '      LABEL_EXPRESSION\n'
			+ '      <div ng-switch-when="edit" class="edit input-group">'
			+ '         <input type="text" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules" fs-num-format="{{fsGridConfig.format}}" fs-grid-filter="fsGridConfig.filter" placeholder="{{fsGridConfig.placeholder|translate}}"'
			+ '           ng-paste="handlePaste($event)" style="text-align: right;"'
			+ '           uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '           tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '		    <span class="input-group-addon range-label">%</span>\n'
			+ '         REQUIRED_EXPRESSION\n'
			+ '      </div>'
			+ '      <div ng-switch-when="readonly" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}" class="input-group">\n'
			+ '          <input disabled type="text" style="text-align: right;" fs-validator="fsGridConfig.rules" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-num-format="{{fsGridConfig.format}}" fs-grid-filter="fsGridConfig.filter">\n'
			+ '	   	     <span class="input-group-addon range-label readonly">%</span>\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="view" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}" class="input-group">\n'
			+ '          <div class="form-control input-style {{fsGridConfig.inputClass}}"><span ng-bind-html="percentageDisplay | trustedHtml"></span><span>%</span></div>\n'
			+ '      </div>\n'
			+ '   </div>\n'
			+ '</div>\n';
		var fsPercentageRange = ''
			+ '<div class="fsPercentageRange form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '   LABEL_EXPRESSION\n'
			+ '   <div ng-switch="formComponentCtrl.showStyle(fsGridConfig)">'
			+ '      <div ng-switch-when="edit" class="edit input-group">\n'
			+ '          <span class="input-group-addon range-label" ng-class="{\'focus\':focused}">{{\'label.global.from\' | translate}}</span>'
			+ '          <input type="text" style="text-align: right;" class="form-control 1st {{::fsGridConfig.inputClass}}" ng-class="{\'focus\':focused}" name="{{::fsGridConfig.model}}.from" ng-model="MODEL_NAME.from" fs-validator="fsGridConfig.rules" fs-num-format="{{fsGridConfig.format}}" fs-grid-filter="fsGridConfig.filter" placeholder="{{fsGridConfig.placeholder|translate}}"'
			+ '            ng-paste="handlePaste($event)" ng-focus="focused = true" ng-blur="focused = false" datepicker-options="_dateOptions" placeholder="{{fsGridConfig.placeholder|translate}}"'
			+ '            uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '            tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}"/>\n'
			+ '          <span class="input-group-addon range-label" ng-class="{\'focus\':focused}" style="border-left: 0;border-right: 0;">{{\'label.global.to\' | translate}}</span>'
			+ '          <input type="text" style="text-align: right;" class="form-control 2nd {{::fsGridConfig.inputClass}}" ng-class="{\'focus\':focused}" name="{{::fsGridConfig.model}}.to" ng-model="MODEL_NAME.to" fs-validator="fsGridConfig.rules" fs-num-format="{{fsGridConfig.format}}" fs-grid-filter="fsGridConfig.filter" placeholder="{{fsGridConfig.placeholder|translate}}"'
			+ '            ng-paste="handlePaste($event)" ng-focus="focused = true" ng-blur="focused = false" datepicker-options="_dateOptions" placeholder="{{fsGridConfig.placeholder|translate}}"'
			+ '            uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '            tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}"/>\n'
			+ '		     <span class="input-group-addon range-label" ng-class="{\'focus\':focused}">%</span>\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="readonly" class="input-group">\n'
			+ '          <span class="input-group-addon range-label readonly">{{\'label.global.from\' | translate}}</span>'
			+ '          <input disabled type="text" style="text-align: right;" class="form-control 1st {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}.from" ng-model="MODEL_NAME.from" fs-num-format="{{fsGridConfig.format}}" fs-grid-filter="fsGridConfig.filter"/>\n'
			+ '          <span class="input-group-addon range-label readonly" style="border-left: 0;border-right: 0;">{{\'label.global.to\' | translate}}</span>'
			+ '          <input disabled type="text" style="text-align: right;" class="form-control 2nd {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}.to" ng-model="MODEL_NAME.to" fs-num-format="{{fsGridConfig.format}}" fs-grid-filter="fsGridConfig.filter"/>\n'
			+ '		     <span class="input-group-addon range-label readonly">%</span>\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="view" class="form-inline">\n'
			+ '         <div class="form-group">\n'
			+ '             <label class="control-label">{{\'label.global.from\' | translate}}</label>'
			+ '             <div class="form-control input-style {{::fsGridConfig.inputClass}}"><span ng-bind-html="fromPercentageDisplay | trustedHtml"></span><span>%</span></div>'
			+ '         </div>\n'
			+ '         <div class="form-group">\n'
			+ '             <label class="control-label">{{\'label.global.to\' | translate}}</label>'
			+ '             <div class="form-control input-style {{::fsGridConfig.inputClass}}"><span ng-bind-html="toPercentageDisplay | trustedHtml"></span><span>%</span></div>'
			+ '         </div>\n'
			+ '      </div>\n'
			+ '   </div>\n'
			+ '</div>\n';
		var fsLookupText = ''
			+ '<div class="fsLookupText form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '   LABEL_EXPRESSION\n'
			+ '   <div ng-switch="formComponentCtrl.showStyle(fsGridConfig)">'
			+ '      <div ng-switch-when="edit" class="edit input-group" ng-click="openCustomDialogs()">\n'
			+ '          <input ng-disabled="!fsGridConfig.options.manualInput || fsGridConfig.filter == \'encode\' " type="text" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-uppercase="fsGridConfig.uppercase" fs-validator="fsGridConfig.rules" placeholder="{{fsGridConfig.placeholder|translate}}"'
			+ '            uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '            tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}" fs-grid-filter="fsGridConfig.filter"/>\n'
			+ '          <span class="input-group-btn">\n'
			+ '              <button ng-readonly="!fsGridConfig.options.manualInput || fsGridConfig.filter == \'encode\'" type="button" context-menu="_contextMenuOptions" class="btn btn-default {{::fsGridConfig.inputClass}}">\n'
			+ '                  <i class="icon icon-{{fsGridConfig.options.icon || \'search\'}}"></i>\n'
			+ '              </button>\n'
			+ '          </span>\n'
			+ '          REQUIRED_EXPRESSION\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="readonly" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '          <input disabled type="text" fs-validator="fsGridConfig.rules" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" placeholder="{{fsGridConfig.placeholder|translate}}" fs-grid-filter="fsGridConfig.filter">'
			+ '      </div>\n'
			+ '      <div ng-switch-when="view" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '          <span class="form-control input-style {{::fsGridConfig.inputClass}}">{{MODEL_NAME MODEL_FILTER}}</span>'
			+ '      </div>\n'
			+ '   </div>\n'
			+ '</div>\n';
		var fsMultipleSelect = ''
			+ '<div class="fsMultipleSelect form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '   LABEL_EXPRESSION\n'
			+ '   <div ng-switch="formComponentCtrl.showStyle(fsGridConfig)">'
			+ '      <div ng-switch-when="edit" class="edit {{::fsGridConfig.postPositionRequired ? \'input-group\': \'\'}}">'
			+ '     	 <select class="form-control {{::fsGridConfig.inputClass}}" multiple size="fsGridConfig.options.length" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules" ng-options="option.value as option.label|translate for option in fsGridConfig.options.data"'
			+ '            uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '            tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '              <option value="" ng-if="fsGridConfig.options.hasEmptyOption"></option>\n'
			+ '     	 </select>\n'
			+ '          REQUIRED_EXPRESSION\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="readonly" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '          <select disabled class="form-control input-sm in" multiple size="fsGridConfig.options.length" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules" ng-options="option.value as option.label|translate for option in fsGridConfig.options.data">\n'
			+ '              <option value="" ng-if="fsGridConfig.options.hasEmptyOption"></option>\n'
			+ '          </select>\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="view" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '          <ul class="list-inline">\n'
			+ '             <li class="{{::fsGridConfig.inputClass}}" ng-repeat="option in fsGridConfig.options.data" ng-if="indexOfModel(option.value) > -1"><span>{{option.label | translate}}</span></li>'
			+ '          </ul>\n'
			+ '      </div>\n'
			+ '   </div>\n'
			+ '</div>\n';
		var fsLookupMultiple = ''
			+ '<div class="fsLookupMultiple form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '   LABEL_EXPRESSION\n'
			+ '   <div ng-switch="formComponentCtrl.showStyle(fsGridConfig)">'
			+ '      <div ng-switch-when="edit" class="edit input-group">\n'
			+ '          <input type="hidden" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules">'
			+ '  	     <select ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" context-menu="_contextMenuOptions" class="form-control {{::fsGridConfig.inputClass}}" multiple size="fsGridConfig.options.length" ng-model="fsGridConfig.fsLookupMultipleModel" ng-options="option.value as option.label|translate for option in fsGridConfig.options.data"'
			+ '             uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '             tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}"></select>\n'
			+ '          <span class="input-group-btn" style="vertical-align: top;">\n'
			+ '              <button type="button" context-menu="removeAllOptions" class="btn btn-default {{::fsGridConfig.inputClass}}" ng-click="openCustomDialogs()">\n'
			+ '                  <i class="icon icon-search"></i>\n'
			+ '              </button>\n'
			+ '          </span>\n'
			+ '          REQUIRED_EXPRESSION\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="readonly" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '          <select disabled class="form-control input-sm in" multiple size="fsGridConfig.options.length" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" ng-options="option.value as option.label|translate for option in fsGridConfig.options.data"></select>\n'
			+ '      </div>\n'
			+ '      <div ng-switch-when="view" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '          <ul class="list-inline">\n'
			+ '             <li class="{{::fsGridConfig.inputClass}}" ng-repeat="option in fsGridConfig.options.data" ng-if="indexOfModel(option.value) > -1"><span>{{option.label | translate}}</span></li>'
			+ '          </ul>\n'
			+ '      </div>\n'
			+ '   </div>\n'
			+ '</div>\n';
		var fsButtons = ''
			+ '<div class="panel panel-default {{::FS_BUTTONS_CONFIG.groupClass}}" ng-if="FS_BUTTONS_CONFIG" ng-switch="!fsGridConfig">\n'
			+ '	   <div ng-switch-when="false" class="fsButton form-group">\n'
			+ '	       <div IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '    	  	   LABEL_EXPRESSION\n'
			+ '			   <div>\n'
			+ '                <fs-button fs-button-config="button" fs-ui-grid="grid" fs-ui-grid-row="row" fs-ui-grid-col="col" ng-repeat="button in FS_BUTTONS_CONFIG.buttons"/>\n'
			+ '		 	   </div>\n'
			+ '	       </div>\n'
			+ '	   </div>\n'
			+ '    <div ng-switch-when="true" class="panel-heading buttons-spacing">\n'
			+ '         <fs-button fs-button-config="button" fs-ui-grid="grid" fs-ui-grid-row="row" fs-ui-grid-col="col" ng-repeat="button in FS_BUTTONS_CONFIG.buttons"/>\n'
			+ '    </div>\n'
			+ '</div>\n';
		var fsButton = ''
			+ '<div ng-switch="isInButtonGroup">\n'
			+ '  <div ng-switch-when="false" class="form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '    LABEL_EXPRESSION\n'
			+ '    <div fs-permission="button" style="margin: 0;padding: 0;outline: 0;">\n'
			+ '        <button class="btn {{::button.btnClass}} {{::button.type}}" ng-attr-type="{{::button.type != \'search\' ? button.type : \'button\'}}"'
			+ '          ng-disabled="!enabled(button)"'
			+ '          TOOLTIPS_EXPRESSION'
			+ '          ng-click="btnClick($event)">\n'
			+ '             <span ng-class="{\'icon icon-{{::button.options.icon}}\' : !(button.options.showIcon == false)}"></span>'
			+ '             <span>{{(button.text || button.label) |translate}}</span>'
			+ '             <span class="icon" ng-if="button.type==\'search\'" ng-class="{\'icon-up\': !isSearchCollapsed(), \'icon-down\': isSearchCollapsed()}"></span>\n'
			+ '        </button>'
			+ '    </div>\n'
			+ '  </div>\n'
			+ '  <div ng-switch-when="true" fs-permission="button">\n'
			+ '    <button class="btn {{button.btnClass}}" ng-attr-type="{{::button.type != \'search\' ? button.type : \'button\'}}"'
			+ '      ng-disabled="!enabled(button)"'
			+ '      TOOLTIPS_EXPRESSION'
			+ '      ng-click="btnClick($event)">\n'
			+ '        <span class="{{::!(button.options.showIcon == false) ? \'icon icon-\' + button.options.icon : \'\'}}"></span>\n'
			+ '        <span>{{button.label|translate}}</span>'
			+ '        SEARCH_BTN_ICON_EXPRESSION\n'
			+ '    </button>\n'
			+ '  </div>'
			+ '</div>';
		var fsFileUpload = ''
			+ '<div class="fsFileUpload row" IS_ACTIVE_SHOW_EXPRESSION>'
			+ '    <input type="hidden" ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules">'
			+ '    <div class="form-group col-sm-4" nv-file-drop uploader="_uploader" options="_fileUploadOptions" accept="_fileUploadOptions.mimeTypes" ng-if="_fileUploadOptions.isMultiple || ((!MODEL_NAME || MODEL_NAME.length == 0) && (!_uploader.queue || _uploader.queue.length == 0))">\n'
			+ '        LABEL_EXPRESSION\n'
			+ '        <label class="well drop-zone" ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" nv-file-over uploader="_uploader" options="_fileUploadOptions" accept="_fileUploadOptions.mimeTypes" >'
			+ '            <span><i class="icon icon-upload"></i><h6>{{fsGridConfig.placeholder|translate}}</h6></span>\n'
			+ '            <input type="file" id="fileSelect" ng-if="_fileUploadOptions.isMultiple != true" nv-file-select uploader="_uploader" options="_fileUploadOptions" accept="{{::_fileUploadOptions.mimeTypes}}" ng-show="false">'
			+ '            <input type="file" id="fileSelect" ng-if="_fileUploadOptions.isMultiple == true" nv-file-select uploader="_uploader" options="_fileUploadOptions" multiple accept="{{::_fileUploadOptions.mimeTypes}}" ng-show="false">'
			+ '        </label>\n'
			+ '        <h5 class="tooltips">{{fsGridConfig.tooltip[0] | translate}}</h5>\n'
			+ '        <select class="form-control {{::fsGridConfig.inputClass}}" ng-if="!fsGridConfig.options.hiddenMimeOptions" ng-disabled="formComponentCtrl.isReadonly(fsGridConfig)" style="display:inline-block;width:auto;min-width:100px;"'
			+ '           ng-if="!fsGridConfig.options.hiddenMimeTypes && !formComponentCtrl.isReadonly(fsGridConfig)" ng-model="fsGridConfig.options.mimeTypes"'
			+ '           ng-options="option.value as option.label|translate for option in fsGridConfig.options.data"></select>'
			+ '    </div>'
			/*+ '    <div class="form-group col-sm-4" ng-repeat="item in _uploader.queue" ng-if="_fileUploadOptions.isMultiple != true && !MODEL_NAME[$index]">'
			+ '        <label class="control-label" ng-if="fsGridConfig.showLabel"><span>{{fsGridConfig.label|translate}}</span></label>\n'
			+ '        <span class="badge badge-sm up pull-right" ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" ng-click="__isOpen=false;_remove(item)"><span class="icon icon-close"></span></span>'
			+ '        <div class="well" align="center">'
			+ '            <i class="icon icon-{{item.icon}}"></i>\n'
			+ '            <h6>{{item.file.name}}</h6>\n'
			+ '            <uib-progressbar type="process" max="\'100\'" value="item.progress"></uib-progressbar>'
			+ '            <span class="progress-bar-text">{{item.progress+\'%\'}}</span>'
			+ '        </div>'
			+ '    </div>\n'*/
			+ '    <div class="form-group col-sm-4" ng-repeat="item in MODEL_NAME" ng-if="_fileUploadOptions.isMultiple != true">'
			+ '        LABEL_EXPRESSION\n'
			+ '        <span class="badge badge-sm up pull-right" ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" ng-click="__isOpen=false;_remove(item)"><span class="icon icon-close"></span></span>'
			+ '        <div class="well" align="center">'
			+ '            <i class="icon icon-{{item.icon}}" ng-click="_open($event, item)" style="cursor: pointer;"></i>\n'
			+ '            <h6 ng-click="_open($event, item)" style="cursor: pointer;">{{item.fileItem ? item.fileItem.file.name : item.docFileName}}</h6>\n'
			+ '            <uib-progressbar type="process" max="\'100\'" value="item.fileItem.progress" ng-if="item.fileItem"></uib-progressbar>'
			+ '            <span class="progress-bar-text" ng-if="item.fileItem">{{item.fileItem.progress+\'%\'}}</span>'
			+ '        </div>'
			+ '    </div>\n'
			+ '    <div class="form-group col-sm-4" ng-repeat="item in MODEL_NAME" ng-if="_fileUploadOptions.isMultiple == true">'
			+ '        LABEL_EXPRESSION\n'
			+ '        <span class="badge badge-sm up pull-right" ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" ng-click="__isOpen=false;_remove(item)"><span class="icon icon-close"></span></span>'
			+ '        <div class="well" ng-switch="item.type" align="center">'
			+ '            <i class="icon icon-{{item.icon}}" ng-click="_open($event, item)" style="cursor: pointer;"></i>\n'
			+ '            <h6 ng-click="_open($event, item)" style="cursor: pointer;">{{item.fileItem ? item.fileItem.file.name : item.docFileName}}</h6>\n'
			+ '            <uib-progressbar type="process" max="\'100\'" value="item.fileItem.progress" ng-if="item.fileItem"></uib-progressbar>'
			+ '            <span class="progress-bar-text" ng-if="item.fileItem">{{item.fileItem.progress+\'%\'}}</span>'
			+ '        </div>'
			+ '    </div>\n'
			+ '</div>\n';
		var tooltipTpl = ''
			+ '<ul class="fstooltip list-inline">\n'
			+ '   <li ng-repeat="message in formComponentCtrl.tooltip">{{message | translate }}</li>\n'
			+ '</ul>\n';
		var fsCard = ""
			+ '<div ng-if="hasFsRadioCard">\n'
			+ '<div>CARD_TEMPLATE</div>\n'
			+ '</div>'
			+ '<div ng-if="!hasFsRadioCard">\n'
			+ '<div class="card" ng-class="{active: (CARD_MODEL_NAME == \'\')}" ng-click="CARD_MODEL_NAME = \'\'" style="cursor: pointer;">CARD_TEMPLATE</div>\n'
			+ '</div>\n';
		var fsListGroup = ''
			+ '<div class="fsListGroup form-group" IS_ACTIVE_SHOW_EXPRESSION>\n'
			+ '  <div class="listGroup panel-group">\n'
			+ '    <fs-scrollbar class="scroller" style="max-height:{{::fsGridConfig.maxHeight}}px;min-height:{{::fsGridConfig.minHeight}}px;" '
			+ '      suppress-scroll-x="true" wheel-propagation="true" refresh-on-change="fsGridConfig.listConfigs" '
			+ '      on-scroll="formComponentCtrl.onScroll" on-scroll-y-end="formComponentCtrl.onScrollYEnd">'
			+ '        <div class="panel" ng-click="formComponentCtrl.selectItem(listConfig)" ng-repeat="listConfig in fsGridConfig.listConfigs" style="cursor: pointer;">\n'
			+ '            LISTITEM_TEMPLATE\n'
			+ '        </div>\n'
			+ '    </fs-scrollbar>'
			+ '  </div>\n';
		+ '</div>\n';

		$templateCache.put('fs/layout/template/fsTabset.html', fsTabset);
		$templateCache.put('fs/layout/template/fsTab.html', fsTab);
		$templateCache.put('fs/layout/template/fsTable.html', fsTable);
		$templateCache.put('fs/layout/template/fsList.html', fsList);
		$templateCache.put('fs/layout/template/fsInnerList.html', fsInnerList);
		$templateCache.put('fs/layout/template/fsGridLayout.html', fsGridLayout);
		$templateCache.put('fs/layout/template/fsAnonymousGroup.html', fsAnonymousGroup);
		$templateCache.put('fs/layout/template/fsSearchGroup.html', fsSearchGroup);
		$templateCache.put('fs/layout/template/fsNormalGroup.html', fsNormalGroup);
		$templateCache.put('fs/layout/template/fsGrid.html', fsGrid);
		$templateCache.put('fs/layout/template/fsColor.html', fsColor);
		$templateCache.put('fs/layout/template/fsLabel.html', fsLabel);
		$templateCache.put('fs/layout/template/fsLink.html', fsLink);
		$templateCache.put('fs/layout/template/fsImage.html', fsImage);
		$templateCache.put('fs/layout/template/fsText.html', fsText);
		$templateCache.put('fs/layout/template/fsPassword.html', fsPassword);
		$templateCache.put('fs/layout/template/fsRadio.html', fsRadio);
		$templateCache.put('fs/layout/template/fsRadioCard.html', fsRadioCard);
		$templateCache.put('fs/layout/template/fsCheckbox.html', fsCheckbox);
		$templateCache.put('fs/layout/template/fsCheckboxGroup.html', fsCheckboxGroup);
		$templateCache.put('fs/layout/template/fsDate.html', fsDate);
		$templateCache.put('fs/layout/template/fsDateRange.html', fsDateRange);
		$templateCache.put('fs/layout/template/fsTime.html', fsTime);
		$templateCache.put('fs/layout/template/fsCalendarDay.html', fsCalendarDay);
		$templateCache.put('fs/layout/template/fsTextarea.html', fsTextarea);
		$templateCache.put('fs/layout/template/fsSelect.html', fsSelect);
		$templateCache.put('fs/layout/template/fsCustomerSelect.html', fsCustomerSelect);
		$templateCache.put('fs/layout/template/fsCustomerSelectItem.html', fsCustomerSelectItem);
		$templateCache.put('fs/layout/template/fsMultipleSelect.html', fsMultipleSelect);
		$templateCache.put('fs/layout/template/fsTypeahead.html', fsTypeahead);
		$templateCache.put('fs/layout/template/fsTypeaheadOption.html', fsTypeaheadOption);
		$templateCache.put('fs/layout/template/fsSensitive.html', fsSensitive);
		$templateCache.put('fs/layout/template/fsMoney.html', fsMoney);
		$templateCache.put('fs/layout/template/fsMoneyRange.html', fsMoneyRange);
		$templateCache.put('fs/layout/template/fsNumber.html', fsNumber);
		$templateCache.put('fs/layout/template/fsNumberRange.html', fsNumberRange);
		$templateCache.put('fs/layout/template/fsPercentage.html', fsPercentage);
		$templateCache.put('fs/layout/template/fsPercentageRange.html', fsPercentageRange);
		$templateCache.put('fs/layout/template/fsLookupText.html', fsLookupText);
		$templateCache.put('fs/layout/template/fsLookupMultiple.html', fsLookupMultiple);
		$templateCache.put('fs/layout/template/fsButtons.html', fsButtons);
		$templateCache.put('fs/layout/template/fsButton.html', fsButton);
		$templateCache.put('fs/layout/template/tooltipTpl.html', tooltipTpl);
		$templateCache.put('fs/layout/template/fsFileUpload.html', fsFileUpload);
		$templateCache.put('fs/layout/template/fsCard.html', fsCard);
		$templateCache.put('fs/layout/template/fsListGroup.html', fsListGroup);

		$templateCache.put('fs/template/tabs/tab.html', ''
			+ '<li ng-class="[{active: active, disabled: disabled}, classes]" class="uib-tab nav-item">\n'
			+ '  <a href ng-click="select($event)" class="nav-link" fs-tab-heading-transclude>{{heading}}</a>\n'
			+ '</li>\n'
			+ '');
		$templateCache.put('fs/template/tabs/tabset.html', ''
			+ '<div>\n'
			+ '  <ul class="nav nav-{{::tabset.type || \'tabs\'}}" ng-class="{\'nav-stacked\': vertical, \'nav-justified\': justified}" ng-transclude></ul>\n'
			+ '  <div class="tab-content">\n'
			+ '    <div class="tab-pane"\n'
			+ '         ng-repeat="tab in tabset.tabs"\n'
			+ '         ng-if="tabset.loaded[tab.index]" ng-class="{active: tabset.active === tab.index}"\n'
			+ '         fs-tab-content-transclude="tab">\n'
			+ '    </div>\n'
			+ '  </div>\n'
			+ '</div>\n'
			+ '');
		$templateCache.put('fs/layout/template/fsDashboardPanel.html', '');
		$templateCache.put('fs/layout/template/calendar/calendarDay.html', ''
			+ '<div ng-switch="datepickerMode">\n'
			+ '    <div uib-daypicker template-url="fs/layout/template/calendar/day.html" ng-switch-when="day" class="uib-daypicker"></div>\n'
			+ '    <div uib-monthpicker ng-switch-when="month" class="uib-monthpicker"></div>\n'
			+ '    <div uib-yearpicker ng-switch-when="year" class="uib-yearpicker"></div>\n'
			+ '</div>');
		$templateCache.put('fs/layout/template/calendar/day.html', ''
			+ '<table role="grid" aria-labelledby="{{::uniqueId}}-title" aria-activedescendant="{{activeDateId}}">\n'
			+ '    <thead>\n'
			+ '        <tr>\n'
			+ '            <th colspan="7" style="text-align: center;"><strong>{{title}}</strong></th>\n'
			+ '        </tr>\n'
			+ '        <tr>\n'
			+ '            <th ng-repeat="label in ::labels track by $index" class="text-center"><small aria-label="{{::label.full}}">{{::label.abbr}}</small></th>\n'
			+ '        </tr>\n'
			+ '    </thead>\n'
			+ '    <tbody>\n'
			+ '        <tr class="uib-weeks" ng-repeat="row in rows track by $index" role="row">\n'
			+ '            <td ng-repeat="dt in row" class="uib-day text-center" role="gridcell" id="{{::dt.uid}}">\n'
			+ '                <span ng-switch="activeDt.date.getMonth() == dt.date.getMonth()">\n'
			+ '                    <button ng-class="datepickerOptions.customClass(dt)" ng-switch-when="true" type="button" class="btn btn-default btn-sm" '
			+ '                      ng-click="::datepickerOptions.toggleHoliday(dt)" onclick="this.blur();" ng-disabled="::datepickerOptions.readonly">\n'
			+ '                        <span>{{::dt.label}}</span>\n'
			+ '                    </button>\n'
			+ '                    <button ng-switch-when="false" type="button" class="btn btn-default btn-sm disabled">&nbsp;</button>\n'
			+ '                </span>\n'
			+ '            </td>\n'
			+ '        </tr>\n'
			+ '    </tbody>\n'
			+ '</table>');
	}

	function initComponent(componentController, fsGridController) {
		componentController.focus = function () {
			return fsGridController.focus();
		};
		componentController.blur = function () {
			return fsGridController.blur();
		};
		componentController.isChanged = function () {
			return fsGridController.isChanged();
		};
		componentController.getTooltipMessage = function () {
			componentController.tooltip = fsGridController.getTooltipMessage();
			return componentController.tooltip;
		};
		componentController.isActive = function (grid) {
			return fsGridController.isActive(grid);
		};
		componentController.isShow = function (grid) {
			return fsGridController.isShow(grid);
		};
		componentController.isReadonly = function (grid) {
			return fsGridController.isReadonly(grid);
		};
		componentController.showStyle = function (grid) {
			return fsGridController.showStyle(grid);
		};
	}
	function watchGridScope(fsGridController, watchExp, listener) {
		return fsGridController.getGridAppScope().$watch(watchExp, function (newVal, oldVal, scope) {
			listener(newVal, oldVal, scope);
		});
	}
	function getTopFormCtrl(formCtrl) {
		return (formCtrl && formCtrl.$$parentForm && formCtrl.$$parentForm[formCtrl.$name]) ? getTopFormCtrl(formCtrl.$$parentForm) : formCtrl;
	}
	function registerFormCtrls(topFormCtrl, formCtrl) {
		if (!formCtrl || topFormCtrl == formCtrl)
			return;
		if (topFormCtrl._formCtrls && topFormCtrl._formCtrls[formCtrl.$$parentForm.$name + "-" + formCtrl.$name]) {
			return;
		}
		topFormCtrl._formCtrls = topFormCtrl._formCtrls || {};
		topFormCtrl._formCtrls[formCtrl.$$parentForm.$name + "-" + formCtrl.$name] = formCtrl;
		if (formCtrl.$$parentForm && formCtrl.$$parentForm[formCtrl.$name]) {
			registerFormCtrls(formCtrl.$$parentForm, formCtrl);
		}
	}

	function destoryFormCtrls(formCtrls, currentFormCtrl) {
		angular.forEach(formCtrls, function (formCtrl) {
			if (formCtrl && formCtrl._formCtrls && formCtrl._formCtrls[currentFormCtrl.$$parentForm.$name + "-" + currentFormCtrl.$name]) {
				delete formCtrl._formCtrls[currentFormCtrl.$$parentForm.$name + "-" + currentFormCtrl.$name];
				destoryFormCtrls(formCtrl._formCtrls, currentFormCtrl);
			}
		});
	}

	function getFormCtrls(formCtrl) {
		if (!formCtrl)
			return null;
		var topFormCtrl = getTopFormCtrl(formCtrl);
		return topFormCtrl._formCtrls;
	}
	function prepareGridRules($parse, gridConfig, validatorConfig) {
		if (!gridConfig || !gridConfig.model || !(validatorConfig || gridConfig.ruleInfos)) {
			return;
		}
		var commonRules, modelName = gridConfig.model, dynamicParamRuleInfos = {};
		dynamicParamRuleInfos.ruleInfos = gridConfig.ruleInfos;
		commonRules = $parse(modelName)(validatorConfig);
		gridConfig.rules = commonRules || dynamicParamRuleInfos;
		if (gridConfig.rules) {
			gridConfig.rules.field = gridConfig.model;
			angular.forEach(gridConfig.rules.ruleInfos, function (rule) {
				if (gridConfig.filter == 'encode') {
					rule.needFilterModelValue = true;
				}
				if (angular.lowercase(rule.ruleName) == 'required' || (rule.conditionValue && (angular.lowercase(rule.ruleName) == 'dependent'))) {
					gridConfig.isRequired = true;
					if (gridConfig.attrName == 'row.entity' && gridConfig.dynamicParameter) {
						gridConfig.postPositionRequired = '<span class="required input-group-addon"></span>';
					} else {
						gridConfig.postPositionRequired = '';
					}
				}
			});
		}
		gridConfig.hasPreparedGridRules = true;
	}
	function buildCompareResult($parse, $filter, formatService, fsGridLayoutController, gridConfig, value, oldValue) {
		if (gridConfig.comparable === false || !gridConfig.model) {
			return;
		}
		var compareResult = {};
		compareResult.hasChanged = true;
		if (gridConfig.type == 'sensitive') {
			oldValue = oldValue || '';
			compareResult.tooltip = [formatService.formatSst(oldValue, gridConfig.options)];
		} else if (gridConfig.filter) {
			compareResult.tooltip = [$parse(gridConfig.model + ' | ' + gridConfig.filter)(fsGridLayoutController.fsVerifyModel)];
		} else if (gridConfig.type == 'select' || gridConfig.type == 'radio' || gridConfig.type == 'checkbox') {
			oldValue = oldValue || '';
			var tooltip = oldValue;
			if (gridConfig.options) {
				if (angular.isString(gridConfig.options)) {
					gridConfig.options = $parse(gridConfig.options)(gridConfig.appScope);
				}
				if (angular.isString(gridConfig.options.data)) {
					gridConfig.options.data = $parse(gridConfig.options.data)(gridConfig.appScope);
				}
				if (gridConfig.options.data && gridConfig.options.data.length) {
					for (var i = 0; i < gridConfig.options.data.length; i++) {
						var obj = gridConfig.options.data[i];
						if (obj.value == oldValue) {
							tooltip = $filter('translate')(obj.label);
							break;
						}
					}
				}
			}
			compareResult.tooltip = [tooltip];
		} else if (angular.isDate(oldValue)) {
			if (gridConfig.type == 'date' || gridConfig.type == 'date-range') {
				compareResult.tooltip = [formatService.formatDate(oldValue, 'date')];
			} else if (gridConfig.type == 'datetime' || gridConfig.type == 'datetime-range') {
				compareResult.tooltip = [formatService.formatDate(oldValue, 'timestamp')];
			} else if (gridConfig.type == 'time') {
				compareResult.tooltip = [formatService.formatDate(oldValue, 'HH:SS')];
			} else {
				compareResult.tooltip = [formatService.formatDate(oldValue, 'longTimestamp')];
			}
		} else {
			compareResult.tooltip = [angular.isObject(oldValue) ? angular.toJson(oldValue) : oldValue];
		}
		return compareResult;
	}
	fsIncludeDirective.$inject = ['$q', '$templateCache', '$templateRequest', '$anchorScroll', '$animate'];
	function fsIncludeDirective($q, $templateCache, $templateRequest, $anchorScroll, $animate) {
		return {
			restrict: 'ECA',
			priority: 400,
			terminal: true,
			transclude: 'element',
			controller: function () { },
			compile: function (element, attr) {
				var srcExp = attr.fsInclude || attr.src, onloadExp = attr.onload || '', autoScrollExp = attr.autoscroll;
				return function (scope, $element, $attr, ctrl, $transclude) {
					var changeCounter = 0, currentScope, previousElement, currentElement;

					var cleanupLastIncludeContent = function () {
						if (previousElement) {
							previousElement.remove();
							previousElement = null;
						}
						if (currentScope) {
							currentScope.$destroy();
							currentScope = null;
						}
						if (currentElement) {
							$animate.leave(currentElement).done(function (response) {
								if (response !== false)
									previousElement = null;
							});
							previousElement = currentElement;
							currentElement = null;
						}
					};

					scope.$watch(srcExp, function fsIncludeWatchAction(src) {
						var afterAnimation = function (response) {
							if (response !== false && angular.isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
								$anchorScroll();
							}
						};
						var thisChangeId = ++changeCounter;

						if (src) {
							//set the 2nd param to true to ignore the template request error so that the inner
							//contents and scope can be cleaned up.
							var promise = $q.resolve($templateCache.get(src));
							promise.then(function (response) {
								if (!response) {
									$templateRequest(src, true).then(function (response) {
										if (scope.$$destroyed) return;

										if (thisChangeId !== changeCounter) return;
										var newScope = scope.$new();
										ctrl.template = response;

										// Note: This will also link all children of ng-include that were contained in the original
										// html. If that content contains controllers, ... they could pollute/change the scope.
										// However, using ng-include on an element with additional content does not make sense...
										// Note: We can't remove them in the cloneAttchFn of $transclude as that
										// function is called before linking the content, which would apply child
										// directives to non existing elements.
										var clone = $transclude(newScope, function (clone) {
											cleanupLastIncludeContent();
											$animate.enter(clone, null, $element).done(afterAnimation);
										});

										currentScope = newScope;
										currentElement = clone;

										currentScope.$emit('$includeContentLoaded', src);
										scope.$eval(onloadExp);
									}, function () {
										if (scope.$$destroyed) return;

										if (thisChangeId === changeCounter) {
											cleanupLastIncludeContent();
											scope.$emit('$includeContentError', src);
										}
									});
								} else {
									if (scope.$$destroyed) return;

									if (thisChangeId !== changeCounter) return;
									var newScope = scope.$new();
									ctrl.template = response;

									// Note: This will also link all children of ng-include that were contained in the original
									// html. If that content contains controllers, ... they could pollute/change the scope.
									// However, using ng-include on an element with additional content does not make sense...
									// Note: We can't remove them in the cloneAttchFn of $transclude as that
									// function is called before linking the content, which would apply child
									// directives to non existing elements.
									var clone = $transclude(newScope, function (clone) {
										cleanupLastIncludeContent();
										$animate.enter(clone, null, $element).done(afterAnimation);
									});

									currentScope = newScope;
									currentElement = clone;

									currentScope.$emit('$includeContentLoaded', src);
									scope.$eval(onloadExp);
								}
							});
							scope.$emit('$includeContentRequested', src);
						} else {
							cleanupLastIncludeContent();
							ctrl.template = null;
						}
					});
				};
			}
		};
	};
})(angular);